From d550cda864a148c22ad79ae1df5579eaf6bef202 Mon Sep 17 00:00:00 2001 From: Tanisha-sharma7302 Date: Thu, 21 May 2026 14:26:35 +0530 Subject: [PATCH 001/123] fix: add empty state with CTA for repositories - issue #179 --- package-lock.json | 26 ++++++++++++++++++++++++-- src/pages/Dashboard.tsx | 19 ++++++++++++++++--- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 015d421e..8a414aa1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -195,7 +195,8 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.2.tgz", "integrity": "sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@electric-sql/pglite-socket": { "version": "0.0.6", @@ -1293,6 +1294,7 @@ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.2.0.tgz", "integrity": "sha512-JdLF8lWZ+LjKGKpBqyAlenxd/kXjd1Abf/xK+6vUA7R7L2Suo6AFTHFRpPSdAKCan9wzdFApsUpSa/F6+t1AtA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/client-runtime-utils": "7.2.0" }, @@ -3171,6 +3173,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -3182,6 +3185,7 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -3247,6 +3251,7 @@ "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", @@ -3684,6 +3689,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4227,6 +4233,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -4937,6 +4944,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -5626,6 +5634,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -5795,6 +5804,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -7086,6 +7096,7 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.10.6.tgz", "integrity": "sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g==", "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -9767,6 +9778,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -9940,6 +9952,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -10153,6 +10166,7 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -10208,6 +10222,7 @@ "integrity": "sha512-jSdHWgWOgFF24+nRyyNRVBIgGDQEsMEF8KPHvhBBg3jWyR9fUAK0Nq9ThUmiGlNgq2FA7vSk/ZoCvefod+a8qg==", "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/config": "7.2.0", "@prisma/dev": "0.17.0", @@ -10349,6 +10364,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -10361,6 +10377,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -10408,6 +10425,7 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -10552,7 +10570,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -11810,6 +11829,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -11895,6 +11915,7 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -12019,6 +12040,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 7a21a2e4..8920c87d 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -290,9 +290,22 @@ export default function Dashboard() { Loading repositories... ) : recentRepositories.length === 0 ? ( -
- No repositories yet. Add your first repository above! -
+
+ +
+

No repositories yet

+

+ Import your first repository to get started +

+
+ +
) : (
{recentRepositories.map((repo) => ( From 30429d7fa86fe938ed648a035769a7b1df57cedb Mon Sep 17 00:00:00 2001 From: Nisshchaya Rathi Date: Thu, 21 May 2026 15:56:00 +0530 Subject: [PATCH 002/123] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- lib/services/githubService.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/services/githubService.ts b/lib/services/githubService.ts index 0b1cbebd..312252b5 100644 --- a/lib/services/githubService.ts +++ b/lib/services/githubService.ts @@ -13,9 +13,27 @@ export function sanitizeGitHubError(error: any) { if (isAxiosError(error) && error.config) { const safeConfig = { ...error.config }; if (safeConfig.headers) { - safeConfig.headers = { ...safeConfig.headers }; - if (safeConfig.headers.Authorization) { - safeConfig.headers.Authorization = "[REDACTED]"; + const headers = safeConfig.headers as any; + const redactedValue = "[REDACTED]"; + + if (typeof headers.set === "function") { + if (typeof headers.get !== "function" || headers.get("Authorization") != null) { + headers.set("Authorization", redactedValue); + } + if (typeof headers.get !== "function" || headers.get("authorization") != null) { + headers.set("authorization", redactedValue); + } + safeConfig.headers = typeof headers.toJSON === "function" ? headers.toJSON() : headers; + } else { + const plainHeaders = + typeof headers.toJSON === "function" ? headers.toJSON() : { ...headers }; + if (plainHeaders.Authorization) { + plainHeaders.Authorization = redactedValue; + } + if (plainHeaders.authorization) { + plainHeaders.authorization = redactedValue; + } + safeConfig.headers = plainHeaders; } } error.config = safeConfig as any; From 35cd81a715410674bbacc1b239ad88957126590a Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:07:20 +0530 Subject: [PATCH 003/123] fix(ai): add strict input validation to all AI routes - chat: validate repositoryId as number, question as non-empty string, conversationHistory structure - analyze-repository: validate repositoryId as number, type against allowed values - analyze-code: validate code, language as non-empty strings, analysisType against allowed values - explain-file: validate repositoryId as number, filePath as non-empty string - suggest-commit: strict array and string type checks - review-pr: remove unreachable fallthrough code --- app/api/ai/analyze-code/route.ts | 21 ++++++++++-- app/api/ai/analyze-repository/route.ts | 24 +++++++++++--- app/api/ai/chat/route.ts | 44 ++++++++++++++++++++++++-- app/api/ai/explain-file/route.ts | 19 +++++++++-- app/api/ai/review-pr/route.ts | 5 --- app/api/ai/suggest-commit/route.ts | 13 ++++---- 6 files changed, 103 insertions(+), 23 deletions(-) diff --git a/app/api/ai/analyze-code/route.ts b/app/api/ai/analyze-code/route.ts index dc2d9e4e..97a0ab51 100644 --- a/app/api/ai/analyze-code/route.ts +++ b/app/api/ai/analyze-code/route.ts @@ -8,9 +8,24 @@ export async function POST(request: NextRequest) { const body = await request.json(); const { code, language, analysisType, context } = body; - if (!code || !language || !analysisType) { + if (typeof code !== "string" || !code.trim()) { return NextResponse.json( - { error: "Code, language, and analysis type are required" }, + { error: "Code must be a non-empty string" }, + { status: 400 } + ); + } + + if (typeof language !== "string" || !language.trim()) { + return NextResponse.json( + { error: "Language must be a non-empty string" }, + { status: 400 } + ); + } + + const validAnalysisTypes = ["explain", "improve", "bugs", "document", "refactor"]; + if (typeof analysisType !== "string" || !validAnalysisTypes.includes(analysisType)) { + return NextResponse.json( + { error: `Analysis type must be one of: ${validAnalysisTypes.join(", ")}` }, { status: 400 } ); } @@ -25,7 +40,7 @@ export async function POST(request: NextRequest) { const analysis = await getGeminiService().analyzeCode({ code, language, - analysisType, + analysisType: analysisType as "explain" | "improve" | "bugs" | "document" | "refactor", context, }); diff --git a/app/api/ai/analyze-repository/route.ts b/app/api/ai/analyze-repository/route.ts index 8e64eece..661488b0 100644 --- a/app/api/ai/analyze-repository/route.ts +++ b/app/api/ai/analyze-repository/route.ts @@ -9,15 +9,31 @@ export async function POST(request: NextRequest) { const body = await request.json(); const { repositoryId, type } = body; - if (!repositoryId || !type) { + if (repositoryId == null || type == null) { return NextResponse.json( { error: "Repository ID and analysis type are required" }, { status: 400 } ); } + const parsedRepoId = Number(repositoryId); + if (!Number.isFinite(parsedRepoId)) { + return NextResponse.json( + { error: "Repository ID must be a valid number" }, + { status: 400 } + ); + } + + const validTypes = ["overview", "code-quality", "security", "architecture", "suggestions"]; + if (typeof type !== "string" || !validTypes.includes(type)) { + return NextResponse.json( + { error: `Analysis type must be one of: ${validTypes.join(", ")}` }, + { status: 400 } + ); + } + const repository = await repositoryService.getRepository( - repositoryId, + parsedRepoId, user.userId ); @@ -45,8 +61,8 @@ export async function POST(request: NextRequest) { }; const analysis = await getGeminiService().analyzeRepository({ - repositoryId, - type, + repositoryId: parsedRepoId, + type: type as "overview" | "code-quality" | "security" | "architecture" | "suggestions", context, }); diff --git a/app/api/ai/chat/route.ts b/app/api/ai/chat/route.ts index 6d258259..1a3b5fa2 100644 --- a/app/api/ai/chat/route.ts +++ b/app/api/ai/chat/route.ts @@ -15,15 +15,53 @@ export async function POST(request: NextRequest) { return NextResponse.json({ response }); } - if (!repositoryId || !question) { + if (prompt !== undefined && typeof prompt !== "string") { + return NextResponse.json( + { error: "Prompt must be a string" }, + { status: 400 } + ); + } + + if (repositoryId == null || question == null) { return NextResponse.json( { error: "Repository ID and question are required" }, { status: 400 } ); } + const parsedRepoId = Number(repositoryId); + if (!Number.isFinite(parsedRepoId)) { + return NextResponse.json( + { error: "Repository ID must be a valid number" }, + { status: 400 } + ); + } + + if (typeof question !== "string" || !question.trim()) { + return NextResponse.json( + { error: "Question must be a non-empty string" }, + { status: 400 } + ); + } + + if ( + conversationHistory !== undefined && + (!Array.isArray(conversationHistory) || + conversationHistory.some( + (m: any) => + typeof m !== "object" || + !["user", "assistant"].includes(m.role) || + typeof m.content !== "string" + )) + ) { + return NextResponse.json( + { error: "conversationHistory must be an array of {role, content} objects" }, + { status: 400 } + ); + } + const repository = await repositoryService.getRepository( - repositoryId, + parsedRepoId, user.userId ); @@ -48,7 +86,7 @@ export async function POST(request: NextRequest) { }; const response = await getGeminiService().chatAboutRepository({ - repositoryId, + repositoryId: parsedRepoId, question, conversationHistory, context, diff --git a/app/api/ai/explain-file/route.ts b/app/api/ai/explain-file/route.ts index 4819b5b0..661f3aab 100644 --- a/app/api/ai/explain-file/route.ts +++ b/app/api/ai/explain-file/route.ts @@ -30,15 +30,30 @@ export async function POST(request: NextRequest) { const body = await request.json(); const { repositoryId, filePath } = body; - if (!repositoryId || !filePath) { + if (repositoryId == null || filePath == null) { return NextResponse.json( { error: "Repository ID and file path are required" }, { status: 400 } ); } + const parsedRepoId = Number(repositoryId); + if (!Number.isFinite(parsedRepoId)) { + return NextResponse.json( + { error: "Repository ID must be a valid number" }, + { status: 400 } + ); + } + + if (typeof filePath !== "string" || !filePath.trim()) { + return NextResponse.json( + { error: "File path must be a non-empty string" }, + { status: 400 } + ); + } + const repository = (await repositoryService.getRepository( - repositoryId, + parsedRepoId, user.userId )) as Repository; diff --git a/app/api/ai/review-pr/route.ts b/app/api/ai/review-pr/route.ts index 0a4c0a90..be0c562c 100644 --- a/app/api/ai/review-pr/route.ts +++ b/app/api/ai/review-pr/route.ts @@ -51,9 +51,4 @@ export async function POST(request: NextRequest) { { status: 500 }, ); } - - return NextResponse.json( - { error: "Failed to review PR", details: "Unexpected fallthrough" }, - { status: 500 }, - ); } diff --git a/app/api/ai/suggest-commit/route.ts b/app/api/ai/suggest-commit/route.ts index aff6393e..8de1db9c 100644 --- a/app/api/ai/suggest-commit/route.ts +++ b/app/api/ai/suggest-commit/route.ts @@ -8,12 +8,13 @@ export async function POST(request: NextRequest) { const body = await request.json(); const { added, modified, deleted, diff } = body; - if ( - (!added || added.length === 0) && - (!modified || modified.length === 0) && - (!deleted || deleted.length === 0) && - !diff - ) { + const hasValidInput = + (Array.isArray(added) && added.length > 0) || + (Array.isArray(modified) && modified.length > 0) || + (Array.isArray(deleted) && deleted.length > 0) || + (typeof diff === "string" && diff.trim().length > 0); + + if (!hasValidInput) { return NextResponse.json( { error: "At least one of added, modified, deleted, or diff is required" }, { status: 400 } From e07e0e729c88bc6311360e0a4486b272f7e205bb Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:18:52 +0530 Subject: [PATCH 004/123] fix(db): add indexes for common worker and API query paths - Repository: add index on userId for listRepositories, getRepository, deleteRepository - Repository: add composite index on [userId, url] for createRepository duplicate check - AnalysisJob: add composite index on [repositoryId, userId, createdAt] for latest job lookup --- prisma/schema.prisma | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8cf7a71d..2a7e1ca9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -179,6 +179,8 @@ model Repository { languages Language[] analysisJobs AnalysisJob[] + @@index([userId]) + @@index([userId, url]) @@map("repositories") } @@ -217,6 +219,7 @@ model AnalysisJob { @@index([lockExpiresAt]) @@index([repositoryId]) @@index([userId]) + @@index([repositoryId, userId, createdAt]) @@map("analysis_jobs") } From a5154ca207b80417498a98940646dea718f92ada Mon Sep 17 00:00:00 2001 From: Nisshchaya Rathi Date: Thu, 21 May 2026 16:21:38 +0530 Subject: [PATCH 005/123] Revert "feat(ui): add skeleton loader for repository readme (#189)" --- app/api/repositories/[id]/readme/route.ts | 8 ------- .../repository/RepositoryOverview.tsx | 21 +------------------ src/components/ui/Skeleton.tsx | 15 ------------- src/components/ui/index.ts | 1 - 4 files changed, 1 insertion(+), 44 deletions(-) delete mode 100644 src/components/ui/Skeleton.tsx diff --git a/app/api/repositories/[id]/readme/route.ts b/app/api/repositories/[id]/readme/route.ts index d460b553..e4aeea6f 100644 --- a/app/api/repositories/[id]/readme/route.ts +++ b/app/api/repositories/[id]/readme/route.ts @@ -1,7 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import { isHttpError, requireAuth } from "@/lib/middleware"; import { repositoryService } from "@/lib/services/repositoryService"; -import { GitHubRateLimitError } from "@/lib/services/githubService"; export async function POST( request: NextRequest, @@ -34,13 +33,6 @@ export async function POST( } catch (error: any) { console.error("Fetch README error:", error); - if (error instanceof GitHubRateLimitError) { - return NextResponse.json( - { error: error.message, retryAfter: error.retryAfterSeconds }, - { status: 429 } - ); - } - if (isHttpError(error)) { return NextResponse.json( { error: error.message }, diff --git a/src/components/repository/RepositoryOverview.tsx b/src/components/repository/RepositoryOverview.tsx index 1588388c..7ff2dc7f 100644 --- a/src/components/repository/RepositoryOverview.tsx +++ b/src/components/repository/RepositoryOverview.tsx @@ -19,7 +19,6 @@ import { CardTitle, CardDescription, CardContent, - Skeleton, } from "@/components/ui"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; @@ -141,9 +140,6 @@ export const RepositoryOverview = ({ })); const hasUsableReadme = Boolean(readmeText && readmeText !== "doesnt exist"); - const isAnalyzing = - repositoryData?.status === "pending" || - repositoryData?.status === "analyzing"; const githubRawBase = (() => { const url = String(repositoryData?.url || ""); @@ -431,22 +427,7 @@ export const RepositoryOverview = ({ - {isAnalyzing && !hasUsableReadme ? ( -
- -
- - - -
-
- - - - -
-
- ) : hasUsableReadme ? ( + {hasUsableReadme ? (
) { - return ( -
- ); -} - -export { Skeleton }; diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index 0ad86fc6..f0d1b29d 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -6,7 +6,6 @@ export { Spinner, LoadingSpinner } from './Spinner' export { Toaster } from './toaster' export { toast } from '@/hooks/use-toast' export { EmptyState } from './EmptyState' -export { Skeleton } from './Skeleton' export { DropdownMenu, DropdownMenuTrigger, From 6870f660589bbb7ae55fa2ed68c9c7349372636f Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Thu, 21 May 2026 16:21:44 +0530 Subject: [PATCH 006/123] chore(db): add userId indexes for query optimization (#205) --- prisma/schema.prisma | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8cf7a71d..b08e5726 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -131,6 +131,7 @@ model Account { user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) + @@index([userId]) @@map("accounts") } @@ -141,6 +142,7 @@ model Session { expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) + @@index([userId]) @@map("sessions") } @@ -179,6 +181,7 @@ model Repository { languages Language[] analysisJobs AnalysisJob[] + @@index([userId]) @@map("repositories") } From b9dd015bc8ad7ae9defb5d556efb7e4ffdbbee38 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:07:20 +0530 Subject: [PATCH 007/123] fix(ai): add strict input validation to all AI routes - chat: validate repositoryId as number, question as non-empty string, conversationHistory structure - analyze-repository: validate repositoryId as number, type against allowed values - analyze-code: validate code, language as non-empty strings, analysisType against allowed values - explain-file: validate repositoryId as number, filePath as non-empty string - suggest-commit: strict array and string type checks - review-pr: remove unreachable fallthrough code --- app/api/ai/analyze-code/route.ts | 21 ++++++++++-- app/api/ai/analyze-repository/route.ts | 24 +++++++++++--- app/api/ai/chat/route.ts | 44 ++++++++++++++++++++++++-- app/api/ai/explain-file/route.ts | 19 +++++++++-- app/api/ai/review-pr/route.ts | 5 --- app/api/ai/suggest-commit/route.ts | 13 ++++---- 6 files changed, 103 insertions(+), 23 deletions(-) diff --git a/app/api/ai/analyze-code/route.ts b/app/api/ai/analyze-code/route.ts index dc2d9e4e..97a0ab51 100644 --- a/app/api/ai/analyze-code/route.ts +++ b/app/api/ai/analyze-code/route.ts @@ -8,9 +8,24 @@ export async function POST(request: NextRequest) { const body = await request.json(); const { code, language, analysisType, context } = body; - if (!code || !language || !analysisType) { + if (typeof code !== "string" || !code.trim()) { return NextResponse.json( - { error: "Code, language, and analysis type are required" }, + { error: "Code must be a non-empty string" }, + { status: 400 } + ); + } + + if (typeof language !== "string" || !language.trim()) { + return NextResponse.json( + { error: "Language must be a non-empty string" }, + { status: 400 } + ); + } + + const validAnalysisTypes = ["explain", "improve", "bugs", "document", "refactor"]; + if (typeof analysisType !== "string" || !validAnalysisTypes.includes(analysisType)) { + return NextResponse.json( + { error: `Analysis type must be one of: ${validAnalysisTypes.join(", ")}` }, { status: 400 } ); } @@ -25,7 +40,7 @@ export async function POST(request: NextRequest) { const analysis = await getGeminiService().analyzeCode({ code, language, - analysisType, + analysisType: analysisType as "explain" | "improve" | "bugs" | "document" | "refactor", context, }); diff --git a/app/api/ai/analyze-repository/route.ts b/app/api/ai/analyze-repository/route.ts index 8e64eece..661488b0 100644 --- a/app/api/ai/analyze-repository/route.ts +++ b/app/api/ai/analyze-repository/route.ts @@ -9,15 +9,31 @@ export async function POST(request: NextRequest) { const body = await request.json(); const { repositoryId, type } = body; - if (!repositoryId || !type) { + if (repositoryId == null || type == null) { return NextResponse.json( { error: "Repository ID and analysis type are required" }, { status: 400 } ); } + const parsedRepoId = Number(repositoryId); + if (!Number.isFinite(parsedRepoId)) { + return NextResponse.json( + { error: "Repository ID must be a valid number" }, + { status: 400 } + ); + } + + const validTypes = ["overview", "code-quality", "security", "architecture", "suggestions"]; + if (typeof type !== "string" || !validTypes.includes(type)) { + return NextResponse.json( + { error: `Analysis type must be one of: ${validTypes.join(", ")}` }, + { status: 400 } + ); + } + const repository = await repositoryService.getRepository( - repositoryId, + parsedRepoId, user.userId ); @@ -45,8 +61,8 @@ export async function POST(request: NextRequest) { }; const analysis = await getGeminiService().analyzeRepository({ - repositoryId, - type, + repositoryId: parsedRepoId, + type: type as "overview" | "code-quality" | "security" | "architecture" | "suggestions", context, }); diff --git a/app/api/ai/chat/route.ts b/app/api/ai/chat/route.ts index 6d258259..1a3b5fa2 100644 --- a/app/api/ai/chat/route.ts +++ b/app/api/ai/chat/route.ts @@ -15,15 +15,53 @@ export async function POST(request: NextRequest) { return NextResponse.json({ response }); } - if (!repositoryId || !question) { + if (prompt !== undefined && typeof prompt !== "string") { + return NextResponse.json( + { error: "Prompt must be a string" }, + { status: 400 } + ); + } + + if (repositoryId == null || question == null) { return NextResponse.json( { error: "Repository ID and question are required" }, { status: 400 } ); } + const parsedRepoId = Number(repositoryId); + if (!Number.isFinite(parsedRepoId)) { + return NextResponse.json( + { error: "Repository ID must be a valid number" }, + { status: 400 } + ); + } + + if (typeof question !== "string" || !question.trim()) { + return NextResponse.json( + { error: "Question must be a non-empty string" }, + { status: 400 } + ); + } + + if ( + conversationHistory !== undefined && + (!Array.isArray(conversationHistory) || + conversationHistory.some( + (m: any) => + typeof m !== "object" || + !["user", "assistant"].includes(m.role) || + typeof m.content !== "string" + )) + ) { + return NextResponse.json( + { error: "conversationHistory must be an array of {role, content} objects" }, + { status: 400 } + ); + } + const repository = await repositoryService.getRepository( - repositoryId, + parsedRepoId, user.userId ); @@ -48,7 +86,7 @@ export async function POST(request: NextRequest) { }; const response = await getGeminiService().chatAboutRepository({ - repositoryId, + repositoryId: parsedRepoId, question, conversationHistory, context, diff --git a/app/api/ai/explain-file/route.ts b/app/api/ai/explain-file/route.ts index 4819b5b0..661f3aab 100644 --- a/app/api/ai/explain-file/route.ts +++ b/app/api/ai/explain-file/route.ts @@ -30,15 +30,30 @@ export async function POST(request: NextRequest) { const body = await request.json(); const { repositoryId, filePath } = body; - if (!repositoryId || !filePath) { + if (repositoryId == null || filePath == null) { return NextResponse.json( { error: "Repository ID and file path are required" }, { status: 400 } ); } + const parsedRepoId = Number(repositoryId); + if (!Number.isFinite(parsedRepoId)) { + return NextResponse.json( + { error: "Repository ID must be a valid number" }, + { status: 400 } + ); + } + + if (typeof filePath !== "string" || !filePath.trim()) { + return NextResponse.json( + { error: "File path must be a non-empty string" }, + { status: 400 } + ); + } + const repository = (await repositoryService.getRepository( - repositoryId, + parsedRepoId, user.userId )) as Repository; diff --git a/app/api/ai/review-pr/route.ts b/app/api/ai/review-pr/route.ts index 0a4c0a90..be0c562c 100644 --- a/app/api/ai/review-pr/route.ts +++ b/app/api/ai/review-pr/route.ts @@ -51,9 +51,4 @@ export async function POST(request: NextRequest) { { status: 500 }, ); } - - return NextResponse.json( - { error: "Failed to review PR", details: "Unexpected fallthrough" }, - { status: 500 }, - ); } diff --git a/app/api/ai/suggest-commit/route.ts b/app/api/ai/suggest-commit/route.ts index aff6393e..8de1db9c 100644 --- a/app/api/ai/suggest-commit/route.ts +++ b/app/api/ai/suggest-commit/route.ts @@ -8,12 +8,13 @@ export async function POST(request: NextRequest) { const body = await request.json(); const { added, modified, deleted, diff } = body; - if ( - (!added || added.length === 0) && - (!modified || modified.length === 0) && - (!deleted || deleted.length === 0) && - !diff - ) { + const hasValidInput = + (Array.isArray(added) && added.length > 0) || + (Array.isArray(modified) && modified.length > 0) || + (Array.isArray(deleted) && deleted.length > 0) || + (typeof diff === "string" && diff.trim().length > 0); + + if (!hasValidInput) { return NextResponse.json( { error: "At least one of added, modified, deleted, or diff is required" }, { status: 400 } From 42db51e4a90f092b2e6fdde7a0e8831461f8a243 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:18:52 +0530 Subject: [PATCH 008/123] fix(db): add indexes for common worker and API query paths - Repository: add index on userId for listRepositories, getRepository, deleteRepository - Repository: add composite index on [userId, url] for createRepository duplicate check - AnalysisJob: add composite index on [repositoryId, userId, createdAt] for latest job lookup --- prisma/schema.prisma | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8cf7a71d..2a7e1ca9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -179,6 +179,8 @@ model Repository { languages Language[] analysisJobs AnalysisJob[] + @@index([userId]) + @@index([userId, url]) @@map("repositories") } @@ -217,6 +219,7 @@ model AnalysisJob { @@index([lockExpiresAt]) @@index([repositoryId]) @@index([userId]) + @@index([repositoryId, userId, createdAt]) @@map("analysis_jobs") } From 7cee0af42b1b2e499333fb7cd68aff9358253f0d Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:22:54 +0530 Subject: [PATCH 009/123] fix(db): add index on GitHubRepo for webhook repo lookup Webhook handler queries gitHubRepo.findFirst({ where: { repoFullName, enabled: true } }) on every pull_request event. Adding a composite index on [repoFullName, enabled] avoids a full table scan when matching incoming webhooks to enabled repos. --- prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2a7e1ca9..fe1b7cd4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -75,6 +75,7 @@ model GitHubRepo { @@unique([userId, repoFullName]) @@index([installationId]) @@index([enabled]) + @@index([repoFullName, enabled]) @@map("github_repos") } From 0ca3ef8dc4c4462bcd8e152500744ab693976e33 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:07:20 +0530 Subject: [PATCH 010/123] fix(ai): add strict input validation to all AI routes - chat: validate repositoryId as number, question as non-empty string, conversationHistory structure - analyze-repository: validate repositoryId as number, type against allowed values - analyze-code: validate code, language as non-empty strings, analysisType against allowed values - explain-file: validate repositoryId as number, filePath as non-empty string - suggest-commit: strict array and string type checks - review-pr: remove unreachable fallthrough code --- app/api/ai/analyze-code/route.ts | 21 ++++++++++-- app/api/ai/analyze-repository/route.ts | 24 +++++++++++--- app/api/ai/chat/route.ts | 44 ++++++++++++++++++++++++-- app/api/ai/explain-file/route.ts | 19 +++++++++-- app/api/ai/review-pr/route.ts | 5 --- app/api/ai/suggest-commit/route.ts | 13 ++++---- 6 files changed, 103 insertions(+), 23 deletions(-) diff --git a/app/api/ai/analyze-code/route.ts b/app/api/ai/analyze-code/route.ts index dc2d9e4e..97a0ab51 100644 --- a/app/api/ai/analyze-code/route.ts +++ b/app/api/ai/analyze-code/route.ts @@ -8,9 +8,24 @@ export async function POST(request: NextRequest) { const body = await request.json(); const { code, language, analysisType, context } = body; - if (!code || !language || !analysisType) { + if (typeof code !== "string" || !code.trim()) { return NextResponse.json( - { error: "Code, language, and analysis type are required" }, + { error: "Code must be a non-empty string" }, + { status: 400 } + ); + } + + if (typeof language !== "string" || !language.trim()) { + return NextResponse.json( + { error: "Language must be a non-empty string" }, + { status: 400 } + ); + } + + const validAnalysisTypes = ["explain", "improve", "bugs", "document", "refactor"]; + if (typeof analysisType !== "string" || !validAnalysisTypes.includes(analysisType)) { + return NextResponse.json( + { error: `Analysis type must be one of: ${validAnalysisTypes.join(", ")}` }, { status: 400 } ); } @@ -25,7 +40,7 @@ export async function POST(request: NextRequest) { const analysis = await getGeminiService().analyzeCode({ code, language, - analysisType, + analysisType: analysisType as "explain" | "improve" | "bugs" | "document" | "refactor", context, }); diff --git a/app/api/ai/analyze-repository/route.ts b/app/api/ai/analyze-repository/route.ts index 8e64eece..661488b0 100644 --- a/app/api/ai/analyze-repository/route.ts +++ b/app/api/ai/analyze-repository/route.ts @@ -9,15 +9,31 @@ export async function POST(request: NextRequest) { const body = await request.json(); const { repositoryId, type } = body; - if (!repositoryId || !type) { + if (repositoryId == null || type == null) { return NextResponse.json( { error: "Repository ID and analysis type are required" }, { status: 400 } ); } + const parsedRepoId = Number(repositoryId); + if (!Number.isFinite(parsedRepoId)) { + return NextResponse.json( + { error: "Repository ID must be a valid number" }, + { status: 400 } + ); + } + + const validTypes = ["overview", "code-quality", "security", "architecture", "suggestions"]; + if (typeof type !== "string" || !validTypes.includes(type)) { + return NextResponse.json( + { error: `Analysis type must be one of: ${validTypes.join(", ")}` }, + { status: 400 } + ); + } + const repository = await repositoryService.getRepository( - repositoryId, + parsedRepoId, user.userId ); @@ -45,8 +61,8 @@ export async function POST(request: NextRequest) { }; const analysis = await getGeminiService().analyzeRepository({ - repositoryId, - type, + repositoryId: parsedRepoId, + type: type as "overview" | "code-quality" | "security" | "architecture" | "suggestions", context, }); diff --git a/app/api/ai/chat/route.ts b/app/api/ai/chat/route.ts index 6d258259..1a3b5fa2 100644 --- a/app/api/ai/chat/route.ts +++ b/app/api/ai/chat/route.ts @@ -15,15 +15,53 @@ export async function POST(request: NextRequest) { return NextResponse.json({ response }); } - if (!repositoryId || !question) { + if (prompt !== undefined && typeof prompt !== "string") { + return NextResponse.json( + { error: "Prompt must be a string" }, + { status: 400 } + ); + } + + if (repositoryId == null || question == null) { return NextResponse.json( { error: "Repository ID and question are required" }, { status: 400 } ); } + const parsedRepoId = Number(repositoryId); + if (!Number.isFinite(parsedRepoId)) { + return NextResponse.json( + { error: "Repository ID must be a valid number" }, + { status: 400 } + ); + } + + if (typeof question !== "string" || !question.trim()) { + return NextResponse.json( + { error: "Question must be a non-empty string" }, + { status: 400 } + ); + } + + if ( + conversationHistory !== undefined && + (!Array.isArray(conversationHistory) || + conversationHistory.some( + (m: any) => + typeof m !== "object" || + !["user", "assistant"].includes(m.role) || + typeof m.content !== "string" + )) + ) { + return NextResponse.json( + { error: "conversationHistory must be an array of {role, content} objects" }, + { status: 400 } + ); + } + const repository = await repositoryService.getRepository( - repositoryId, + parsedRepoId, user.userId ); @@ -48,7 +86,7 @@ export async function POST(request: NextRequest) { }; const response = await getGeminiService().chatAboutRepository({ - repositoryId, + repositoryId: parsedRepoId, question, conversationHistory, context, diff --git a/app/api/ai/explain-file/route.ts b/app/api/ai/explain-file/route.ts index 4819b5b0..661f3aab 100644 --- a/app/api/ai/explain-file/route.ts +++ b/app/api/ai/explain-file/route.ts @@ -30,15 +30,30 @@ export async function POST(request: NextRequest) { const body = await request.json(); const { repositoryId, filePath } = body; - if (!repositoryId || !filePath) { + if (repositoryId == null || filePath == null) { return NextResponse.json( { error: "Repository ID and file path are required" }, { status: 400 } ); } + const parsedRepoId = Number(repositoryId); + if (!Number.isFinite(parsedRepoId)) { + return NextResponse.json( + { error: "Repository ID must be a valid number" }, + { status: 400 } + ); + } + + if (typeof filePath !== "string" || !filePath.trim()) { + return NextResponse.json( + { error: "File path must be a non-empty string" }, + { status: 400 } + ); + } + const repository = (await repositoryService.getRepository( - repositoryId, + parsedRepoId, user.userId )) as Repository; diff --git a/app/api/ai/review-pr/route.ts b/app/api/ai/review-pr/route.ts index 0a4c0a90..be0c562c 100644 --- a/app/api/ai/review-pr/route.ts +++ b/app/api/ai/review-pr/route.ts @@ -51,9 +51,4 @@ export async function POST(request: NextRequest) { { status: 500 }, ); } - - return NextResponse.json( - { error: "Failed to review PR", details: "Unexpected fallthrough" }, - { status: 500 }, - ); } diff --git a/app/api/ai/suggest-commit/route.ts b/app/api/ai/suggest-commit/route.ts index aff6393e..8de1db9c 100644 --- a/app/api/ai/suggest-commit/route.ts +++ b/app/api/ai/suggest-commit/route.ts @@ -8,12 +8,13 @@ export async function POST(request: NextRequest) { const body = await request.json(); const { added, modified, deleted, diff } = body; - if ( - (!added || added.length === 0) && - (!modified || modified.length === 0) && - (!deleted || deleted.length === 0) && - !diff - ) { + const hasValidInput = + (Array.isArray(added) && added.length > 0) || + (Array.isArray(modified) && modified.length > 0) || + (Array.isArray(deleted) && deleted.length > 0) || + (typeof diff === "string" && diff.trim().length > 0); + + if (!hasValidInput) { return NextResponse.json( { error: "At least one of added, modified, deleted, or diff is required" }, { status: 400 } From a31fc98026ae935d64af9608b9f52a69fd22acde Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:18:52 +0530 Subject: [PATCH 011/123] fix(db): add indexes for common worker and API query paths - Repository: add index on userId for listRepositories, getRepository, deleteRepository - Repository: add composite index on [userId, url] for createRepository duplicate check - AnalysisJob: add composite index on [repositoryId, userId, createdAt] for latest job lookup --- prisma/schema.prisma | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8cf7a71d..2a7e1ca9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -179,6 +179,8 @@ model Repository { languages Language[] analysisJobs AnalysisJob[] + @@index([userId]) + @@index([userId, url]) @@map("repositories") } @@ -217,6 +219,7 @@ model AnalysisJob { @@index([lockExpiresAt]) @@index([repositoryId]) @@index([userId]) + @@index([repositoryId, userId, createdAt]) @@map("analysis_jobs") } From a4fd1660038c34e74b00d23511ea9c4598a2e5cd Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:22:54 +0530 Subject: [PATCH 012/123] fix(db): add index on GitHubRepo for webhook repo lookup Webhook handler queries gitHubRepo.findFirst({ where: { repoFullName, enabled: true } }) on every pull_request event. Adding a composite index on [repoFullName, enabled] avoids a full table scan when matching incoming webhooks to enabled repos. --- prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2a7e1ca9..fe1b7cd4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -75,6 +75,7 @@ model GitHubRepo { @@unique([userId, repoFullName]) @@index([installationId]) @@index([enabled]) + @@index([repoFullName, enabled]) @@map("github_repos") } From 5d92af0fce9d0841ae586c1e9a573ba7f6f02283 Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Thu, 21 May 2026 16:27:32 +0530 Subject: [PATCH 013/123] docs: add integrations rate limit and retry robustness guide (#206) --- GOOGLE_OAUTH_SETUP.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/GOOGLE_OAUTH_SETUP.md b/GOOGLE_OAUTH_SETUP.md index 59ac0e79..fa79476a 100644 --- a/GOOGLE_OAUTH_SETUP.md +++ b/GOOGLE_OAUTH_SETUP.md @@ -120,6 +120,37 @@ When deploying to production: 3. Ensure all environment variables are set in your production environment 4. Run the database migration in production +## Provider Rate Limits & Retry Strategies + +When GitVerse integrates with third-party providers (like GitHub or Google), it natively handles rate limiting and transient failures to ensure robust operation. + +### Rate Limit Behavior (HTTP 429) + +If a provider returns a `429 Too Many Requests` response, GitVerse will safely catch the error and surface a standardized response to the client. The frontend will typically display a message like: +`"Rate limit reached. Please retry after X seconds."` + +GitVerse extracts the `retryAfter` or reset timing directly from the provider's HTTP headers and propagates it safely without exposing internal API responses. + +### Retry with Exponential Backoff + +For transient network failures or safe server errors (e.g., `502`, `503`, `504`), GitVerse employs an automatic exponential backoff mechanism. + +**Retry Flow Example:** +- Attempt 1: Immediate Execution +- Attempt 2: 1s delay +- Attempt 3: 2s delay +- Attempt 4: 4s delay + +This prevents infinite retry loops and respects provider rate limits while improving overall system reliability. + +## Secure Logging Practices + +To protect sensitive information, GitVerse enforces strict token sanitization requirements across all provider integrations: + +1. **Token Redaction:** Never log raw OAuth access tokens, `Authorization` headers, or provider credentials. +2. **Error Sanitization:** If an HTTP fetch fails, the underlying `AxiosError` or `fetch` error is sanitized to strip out sensitive config and request headers before it reaches `console.error`. +3. **Safe Debugging:** Logs will output the HTTP status code, safe message strings, and endpoint paths (excluding query string secrets) to preserve debuggability without compromising security. + ## Troubleshooting ### Error: "redirect_uri_mismatch" From 1f2b858d454420abca01257682bd2ce5442e99f2 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:29:57 +0530 Subject: [PATCH 014/123] fix(db): add indexes on Account and Session for user-related queries Account: add @@index([userId, provider]) to speed up count and deleteMany queries filtering by user and provider (used in users/me, users/profile, auth/signup, auth/login). Session: add @@index([userId]) to speed up session lookups by user. --- prisma/schema.prisma | 2 ++ 1 file changed, 2 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fe1b7cd4..073f38e3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -132,6 +132,7 @@ model Account { user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) + @@index([userId, provider]) @@map("accounts") } @@ -142,6 +143,7 @@ model Session { expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) + @@index([userId]) @@map("sessions") } From 6bfbcd00d2eb123461a83c2fbfb3f8ee0236b1b0 Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Thu, 21 May 2026 16:33:07 +0530 Subject: [PATCH 015/123] docs: enforce ownership and no-store caching strategy (#176) --- GIT_GRAPH_SETUP.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/GIT_GRAPH_SETUP.md b/GIT_GRAPH_SETUP.md index e7ed0c1b..ce84ea24 100644 --- a/GIT_GRAPH_SETUP.md +++ b/GIT_GRAPH_SETUP.md @@ -79,3 +79,35 @@ The graph algorithm uses these parent relationships to: - Detect when branches merge - Position commits in the correct columns - Show branch splits and joins + +## Security & Access Control + +To protect private repository data and analysis results, GitVerse enforces strict ownership and caching rules across all user-scoped API endpoints. + +### Ownership Enforcement Strategy + +Before returning sensitive data (e.g., repository insights or commit history), the backend middleware must validate that the authenticated user actually owns the requested resource. +- **Authorized:** If `repository.userId === user.id`, the data is safely returned. +- **Unauthorized (404/403):** If a user attempts to access another user's private data, the API will intentionally return a generic `404 Not Found` or `403 Forbidden` to prevent leaking the existence or metadata of private resources. + +### Cache-Control: `no-store` + +All endpoints returning private or user-scoped data explicitly define HTTP caching directives to prevent accidental data leakage via proxies or browsers. + +**Example Response Header:** +```http +Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate +``` + +This guarantees that: +- Browsers never cache sensitive repository data. +- CDNs or edge proxies do not mistakenly serve one user's data to another. +- API requests always hit the origin server to re-verify authentication and ownership. + +### Best Practices for Protected Endpoints + +When developing new GitVerse API routes, always adhere to the following pattern: +1. Extract and validate the session `user`. +2. Fetch the resource and perform an ownership check. +3. Return `404 Not Found` if the ownership check fails. +4. Append `Cache-Control: no-store` to the `NextResponse` before returning. From 28810e60d072ecd33daf64a8bbe7afd6e2939535 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:39:00 +0530 Subject: [PATCH 016/123] fix(db): add indexes on Branch, Contributor, and Language for repository queries Branch, Contributor, and Language models are frequently queried by repositoryId in stats and listing endpoints. Adding explicit @@index([repositoryId]) on each model provides a dedicated non-unique index optimized for count and list operations, separate from the unique constraint indexes. --- prisma/schema.prisma | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 073f38e3..582c1d9b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -239,6 +239,7 @@ model Branch { updatedAt DateTime @updatedAt @map("updated_at") @@unique([repositoryId, name]) + @@index([repositoryId]) @@map("branches") } @@ -321,6 +322,7 @@ model Contributor { updatedAt DateTime @updatedAt @map("updated_at") @@unique([repositoryId, email]) + @@index([repositoryId]) @@map("contributors") } @@ -336,5 +338,6 @@ model Language { createdAt DateTime @default(now()) @map("created_at") @@unique([repositoryId, name]) + @@index([repositoryId]) @@map("languages") } From 2a48da18ac3571ed18e249358b43843252449616 Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Thu, 21 May 2026 16:41:32 +0530 Subject: [PATCH 017/123] feat(webhooks): enforce ownership checks and no-store caching (#183) --- .../integrations/github/pr-reviews/route.ts | 18 +++++++++++++++++- app/api/webhooks/github/route.ts | 10 +++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/api/integrations/github/pr-reviews/route.ts b/app/api/integrations/github/pr-reviews/route.ts index e3651873..02e5c480 100644 --- a/app/api/integrations/github/pr-reviews/route.ts +++ b/app/api/integrations/github/pr-reviews/route.ts @@ -63,7 +63,23 @@ export async function GET(request: NextRequest) { }, }); - return NextResponse.json({ repos: toJsonSafe(repos) }, { status: 200 }); + if (repoFullName && repos.length === 0) { + return NextResponse.json( + { error: "Repository not found" }, + { status: 404 }, + ); + } + + return NextResponse.json( + { repos: toJsonSafe(repos) }, + { + status: 200, + headers: { + "Cache-Control": + "no-store, no-cache, must-revalidate, proxy-revalidate", + }, + }, + ); } catch (error: any) { console.error("GitHub PR reviews error:", error); if (isHttpError(error)) { diff --git a/app/api/webhooks/github/route.ts b/app/api/webhooks/github/route.ts index 8737785c..7bc419bd 100644 --- a/app/api/webhooks/github/route.ts +++ b/app/api/webhooks/github/route.ts @@ -291,7 +291,15 @@ export async function POST(request: NextRequest) { { status: 200 }, ); } catch (error: any) { - console.error("GitHub webhook PR review error:", error); + const safeError = isAxiosError(error) + ? { + message: error.message, + status: error.response?.status, + data: error.response?.data, + url: error.config?.url, + } + : error; + console.error("GitHub webhook PR review error:", safeError); return NextResponse.json( { error: "Failed to process PR webhook", From b1eb108b3cca2a24809f671674e26eb09627818a Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:22:54 +0530 Subject: [PATCH 018/123] fix(db): add index on GitHubRepo for webhook repo lookup Webhook handler queries gitHubRepo.findFirst({ where: { repoFullName, enabled: true } }) on every pull_request event. Adding a composite index on [repoFullName, enabled] avoids a full table scan when matching incoming webhooks to enabled repos. --- prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2a7e1ca9..fe1b7cd4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -75,6 +75,7 @@ model GitHubRepo { @@unique([userId, repoFullName]) @@index([installationId]) @@index([enabled]) + @@index([repoFullName, enabled]) @@map("github_repos") } From 6064f5c330a41753d8ab0db4650f073a890b6b35 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:29:57 +0530 Subject: [PATCH 019/123] fix(db): add indexes on Account and Session for user-related queries Account: add @@index([userId, provider]) to speed up count and deleteMany queries filtering by user and provider (used in users/me, users/profile, auth/signup, auth/login). Session: add @@index([userId]) to speed up session lookups by user. --- prisma/schema.prisma | 2 ++ 1 file changed, 2 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fe1b7cd4..073f38e3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -132,6 +132,7 @@ model Account { user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) + @@index([userId, provider]) @@map("accounts") } @@ -142,6 +143,7 @@ model Session { expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) + @@index([userId]) @@map("sessions") } From 6f3942c5c5bcb117ce71dd8e6135d9821f9d498b Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:39:00 +0530 Subject: [PATCH 020/123] fix(db): add indexes on Branch, Contributor, and Language for repository queries Branch, Contributor, and Language models are frequently queried by repositoryId in stats and listing endpoints. Adding explicit @@index([repositoryId]) on each model provides a dedicated non-unique index optimized for count and list operations, separate from the unique constraint indexes. --- prisma/schema.prisma | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 073f38e3..582c1d9b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -239,6 +239,7 @@ model Branch { updatedAt DateTime @updatedAt @map("updated_at") @@unique([repositoryId, name]) + @@index([repositoryId]) @@map("branches") } @@ -321,6 +322,7 @@ model Contributor { updatedAt DateTime @updatedAt @map("updated_at") @@unique([repositoryId, email]) + @@index([repositoryId]) @@map("contributors") } @@ -336,5 +338,6 @@ model Language { createdAt DateTime @default(now()) @map("created_at") @@unique([repositoryId, name]) + @@index([repositoryId]) @@map("languages") } From 221aae4f58f3a81f8ac30a23b0ec5b2ef2f0d685 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:44:43 +0530 Subject: [PATCH 021/123] fix(db): add index on AnalysisJob for user status queries AnalysisJob: add @@index([userId, status]) to speed up queries filtering by user and job status (used in job listing and status filtering endpoints). --- prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 582c1d9b..14541342 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -223,6 +223,7 @@ model AnalysisJob { @@index([repositoryId]) @@index([userId]) @@index([repositoryId, userId, createdAt]) + @@index([userId, status]) @@map("analysis_jobs") } From b8d78a323437578a279fb02a53898ba933c5dcdc Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Thu, 21 May 2026 16:48:55 +0530 Subject: [PATCH 022/123] feat(security): add server-side validation for repository identifiers (#63) --- .../integrations/github/pr-reviews/route.ts | 9 ++++++ .../integrations/github/select-repos/route.ts | 8 +++++ lib/utils/validators.ts | 29 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 lib/utils/validators.ts diff --git a/app/api/integrations/github/pr-reviews/route.ts b/app/api/integrations/github/pr-reviews/route.ts index 02e5c480..46045eb9 100644 --- a/app/api/integrations/github/pr-reviews/route.ts +++ b/app/api/integrations/github/pr-reviews/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; import { isHttpError, requireAuth } from "@/lib/middleware"; import prisma from "@/lib/prisma"; import { toJsonSafe } from "@/lib/utils/jsonSafe"; +import { isValidRepositoryIdentifier } from "@/lib/utils/validators"; function clampInt( value: string | null, @@ -20,6 +21,14 @@ export async function GET(request: NextRequest) { const url = new URL(request.url); const repoFullName = (url.searchParams.get("repoFullName") || "").trim(); + + if (repoFullName && !isValidRepositoryIdentifier(repoFullName)) { + return NextResponse.json( + { error: "Invalid repository identifier format" }, + { status: 400 } + ); + } + const includeDisabled = (url.searchParams.get("includeDisabled") || "") .trim() .toLowerCase(); diff --git a/app/api/integrations/github/select-repos/route.ts b/app/api/integrations/github/select-repos/route.ts index f33bb924..b9b4a585 100644 --- a/app/api/integrations/github/select-repos/route.ts +++ b/app/api/integrations/github/select-repos/route.ts @@ -3,6 +3,7 @@ import { isHttpError, requireAuth } from "@/lib/middleware"; import prisma from "@/lib/prisma"; import { toJsonSafe } from "@/lib/utils/jsonSafe"; import { GitHubRateLimitError } from "@/lib/services/githubService"; +import { isValidRepositoryIdentifier } from "@/lib/utils/validators"; export async function POST(request: NextRequest) { try { @@ -21,6 +22,13 @@ export async function POST(request: NextRequest) { ); } + if (!repoFullNames.every(isValidRepositoryIdentifier)) { + return NextResponse.json( + { error: "Invalid repository identifier format in selection" }, + { status: 400 }, + ); + } + // Upsert selected repos as enabled. await prisma.$transaction(async (tx) => { for (const fullName of repoFullNames) { diff --git a/lib/utils/validators.ts b/lib/utils/validators.ts new file mode 100644 index 00000000..4b80217b --- /dev/null +++ b/lib/utils/validators.ts @@ -0,0 +1,29 @@ +/** + * Validates a GitHub repository identifier format (owner/repo). + * Allowed format: alphanumeric, hyphens, underscores, and periods. + * Max length: 150 characters to prevent DoS. + * Prevents path traversal patterns like ../ or excessive slashes. + */ +export function isValidRepositoryIdentifier( + identifier: string | null | undefined, +): boolean { + if (!identifier || typeof identifier !== "string") return false; + + if (identifier.length > 150 || identifier.length < 3) return false; + + // Strict regex: Owner/Repo (1 slash exactly) + // Each segment: 1 to 100 characters of [A-Za-z0-9_.-] + const regex = /^[A-Za-z0-9_.-]{1,100}\/[A-Za-z0-9_.-]{1,100}$/; + if (!regex.test(identifier)) return false; + + // Additional check to prevent encoded traversals or direct traversal matches + if ( + identifier.includes("..") || + identifier.startsWith("/") || + identifier.endsWith("/") + ) { + return false; + } + + return true; +} From 1adcedff60963a1a63a038607d69cdb383755430 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:49:32 +0530 Subject: [PATCH 023/123] fix(db): add index on Session for expiry cleanup queries Session: add @@index([expires]) to speed up session cleanup queries that filter by expiry date. NextAuth and custom auth flows periodically delete expired sessions, and this index avoids a full table scan. --- prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 14541342..ec3aa93f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -144,6 +144,7 @@ model Session { user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) + @@index([expires]) @@map("sessions") } From efdfa7cdc6fa314815448db4a6cda2447b6a88a9 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:49:32 +0530 Subject: [PATCH 024/123] fix(db): add index on Session for expiry cleanup queries Session: add @@index([expires]) to speed up session cleanup queries that filter by expiry date. NextAuth and custom auth flows periodically delete expired sessions, and this index avoids a full table scan. --- prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 14541342..ec3aa93f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -144,6 +144,7 @@ model Session { user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) + @@index([expires]) @@map("sessions") } From 35e64511541b38c57dd0c8c7951a219492611ba3 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:53:09 +0530 Subject: [PATCH 025/123] fix: validate job ID format in analysis job endpoints Add UUID format validation to both analysis job routes to reject invalid job IDs early with a 400 response instead of returning a misleading 404. - app/api/analysis-jobs/[id]/route.ts: validate jobId is valid UUID - app/api/analysis/[jobId]/route.ts: validate jobId is valid UUID --- app/api/analysis-jobs/[id]/route.ts | 8 ++++++++ app/api/analysis/[jobId]/route.ts | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/app/api/analysis-jobs/[id]/route.ts b/app/api/analysis-jobs/[id]/route.ts index b9bf0263..e1e4cf9e 100644 --- a/app/api/analysis-jobs/[id]/route.ts +++ b/app/api/analysis-jobs/[id]/route.ts @@ -11,6 +11,14 @@ export async function GET( const user = await requireAuth(request); const jobId = params.id; + const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + if (!uuidPattern.test(jobId)) { + return NextResponse.json( + { error: "Invalid job ID format. Expected a UUID" }, + { status: 400 } + ); + } + const job = await analysisJobService.getJob({ jobId, userId: user.userId, diff --git a/app/api/analysis/[jobId]/route.ts b/app/api/analysis/[jobId]/route.ts index 55451a28..75fdcf8b 100644 --- a/app/api/analysis/[jobId]/route.ts +++ b/app/api/analysis/[jobId]/route.ts @@ -14,6 +14,14 @@ export async function GET( return NextResponse.json({ error: "Missing jobId" }, { status: 400 }); } + const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + if (!uuidPattern.test(jobId)) { + return NextResponse.json( + { error: "Invalid job ID format. Expected a UUID" }, + { status: 400 } + ); + } + const job = await analysisJobService.getJob({ jobId, userId: user.userId }); if (!job) { From 272842cdb3fe4a3b2becb3666eefae7cff0fcfb8 Mon Sep 17 00:00:00 2001 From: Tuba Date: Thu, 21 May 2026 16:57:54 +0530 Subject: [PATCH 026/123] docs: unify onboarding documentation references --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 7f4575b6..b3e1c53c 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,19 @@ Paste a repo → GitVerse builds a visual map + AI onboarding so contributors ca - Ask AI questions about files, folders, and architecture - Generate analysis jobs and track progress +## Getting Started + +The canonical onboarding and setup guide is: + +- [GETTING_STARTED.md](./GETTING_STARTED.md) + +Additional setup docs: + +- [START_HERE.md](./START_HERE.md) +- [GOOGLE_OAUTH_SETUP.md](./GOOGLE_OAUTH_SETUP.md) +- [GOOGLE_OAUTH_INTEGRATION.md](./GOOGLE_OAUTH_INTEGRATION.md) +- [QUICKSTART_OAUTH.md](./QUICKSTART_OAUTH.md) + ## Quickstart (local dev) ```bash From 44a9a2e89ec5545a3e15c8fb47988986344d9358 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:59:06 +0530 Subject: [PATCH 027/123] feat(ui): add skeleton loaders for analysis job page Replace simple loading text with a skeleton UI that matches the final layout to reduce layout shift when data loads. - Add Skeleton component in src/components/ui/Skeleton.tsx - Add RepositoryAnalysisSkeleton component that mirrors the page layout - Update RepositoryAnalysis.tsx to use the skeleton during loading --- .../ui/RepositoryAnalysisSkeleton.tsx | 39 +++++++++++++++++++ src/components/ui/Skeleton.tsx | 13 +++++++ src/pages/RepositoryAnalysis.tsx | 5 +-- 3 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 src/components/ui/RepositoryAnalysisSkeleton.tsx create mode 100644 src/components/ui/Skeleton.tsx diff --git a/src/components/ui/RepositoryAnalysisSkeleton.tsx b/src/components/ui/RepositoryAnalysisSkeleton.tsx new file mode 100644 index 00000000..fa9db0d4 --- /dev/null +++ b/src/components/ui/RepositoryAnalysisSkeleton.tsx @@ -0,0 +1,39 @@ +import { Skeleton } from "./Skeleton"; + +export const RepositoryAnalysisSkeleton: React.FC = () => { + return ( +
+ {/* Header skeleton */} +
+ +
+ + +
+ +
+
+ +
+ + {/* Tab navigation skeleton */} +
+
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+
+ + {/* Content skeleton */} +
+ + + + + + +
+
+ ); +}; diff --git a/src/components/ui/Skeleton.tsx b/src/components/ui/Skeleton.tsx new file mode 100644 index 00000000..f30a7552 --- /dev/null +++ b/src/components/ui/Skeleton.tsx @@ -0,0 +1,13 @@ +import { cn } from "@/lib/utils"; + +interface SkeletonProps { + className?: string; +} + +export const Skeleton: React.FC = ({ className }) => { + return ( +
+ ); +}; diff --git a/src/pages/RepositoryAnalysis.tsx b/src/pages/RepositoryAnalysis.tsx index a095c6f6..d9ec717a 100644 --- a/src/pages/RepositoryAnalysis.tsx +++ b/src/pages/RepositoryAnalysis.tsx @@ -27,6 +27,7 @@ import { useRouter } from "next/navigation"; import axios from "axios"; import { useToast } from "@/hooks/use-toast"; import { buildApiUrl } from "@/services/apiConfig"; +import { RepositoryAnalysisSkeleton } from "@/components/ui/RepositoryAnalysisSkeleton"; type TabType = | "overview" @@ -223,9 +224,7 @@ export default function RepositoryAnalysis() {
{loading ? ( -
-

Loading repository...

-
+ ) : !job ? (
From a5f89f404e0ddcad390ef41148d1235a087cc7c1 Mon Sep 17 00:00:00 2001 From: Harshit Bhardwaj Date: Thu, 21 May 2026 17:01:58 +0530 Subject: [PATCH 028/123] fix: add validation and proper error handling for logout api --- app/api/auth/logout/route.ts | 50 +++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/app/api/auth/logout/route.ts b/app/api/auth/logout/route.ts index 3f9583c1..5c8b9460 100644 --- a/app/api/auth/logout/route.ts +++ b/app/api/auth/logout/route.ts @@ -2,13 +2,51 @@ import { NextRequest, NextResponse } from "next/server"; import { getAuthUser } from "@/lib/middleware"; export async function POST(request: NextRequest) { - const user = await getAuthUser(request); + try { + const contentType = request.headers.get("content-type"); - if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }); - } + if (!contentType || !contentType.includes("application/json")) { + return NextResponse.json( + { error: "Content-Type must be application/json"}, + { status: 400 } + ); + } + + try { + await request.json(); + } catch { + return NextResponse.json( + { error: "Invalid JSON payload"}, + { status: 400 } + ); + } + + //Authentication user + const user = await getAuthUser(request); + + if (!user) { + return NextResponse.json( + { error: "Not authenticated" }, + { status: 401 }); + } + + //Logout response // In a stateless JWT setup, logout is handled client-side by removing the token // We can optionally implement token blacklisting here if needed - return NextResponse.json({ message: "Logged out successfully" }); -} + + return NextResponse.json( + { message: "Logged out successfully" }, + { status: 200 } + ); + +} catch (error) { + console.error("Logout API Error:", error); + + //prevent stack trace from reaching client + return NextResponse.json( + { error: "Something went wrong" }, + { status: 400 } + ); + } +} \ No newline at end of file From 3e7fdcabed4c1875d35c3a61431fab83f907bf79 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 17:04:38 +0530 Subject: [PATCH 029/123] fix(security): enforce ownership and no-store caching on webhook endpoint - Add Cache-Control: no-store to all webhook responses to prevent caching - Remove internal details from error responses to prevent information leakage - Add ownership verification to repo lookup query - Extract noStoreResponse helper for consistent response handling --- app/api/webhooks/github/route.ts | 100 +++++++++++-------------------- 1 file changed, 36 insertions(+), 64 deletions(-) diff --git a/app/api/webhooks/github/route.ts b/app/api/webhooks/github/route.ts index eba6ba72..5cbfb86a 100644 --- a/app/api/webhooks/github/route.ts +++ b/app/api/webhooks/github/route.ts @@ -36,6 +36,13 @@ function shouldHandlePullRequestAction(action: string | undefined): boolean { ); } +function noStoreResponse(data: unknown, status = 200) { + return NextResponse.json(data, { + status, + headers: { "Cache-Control": "no-store" }, + }); +} + export async function POST(request: NextRequest) { const rawBody = await request.text(); @@ -50,45 +57,33 @@ export async function POST(request: NextRequest) { webhookSecret: secret, }) ) { - return NextResponse.json({ error: "Invalid signature" }, { status: 401 }); + return noStoreResponse({ error: "Invalid signature" }, 401); } if (event !== "pull_request") { - return NextResponse.json( - { ok: true, ignored: true, event }, - { status: 200 }, - ); + return noStoreResponse({ ok: true, ignored: true, event }); } let payload: PullRequestWebhookPayload; try { payload = JSON.parse(rawBody); } catch { - return NextResponse.json({ error: "Invalid JSON" }, { status: 400 }); + return noStoreResponse({ error: "Invalid JSON" }, 400); } const action = payload.action; if (!shouldHandlePullRequestAction(action)) { - return NextResponse.json( - { ok: true, ignored: true, action }, - { status: 200 }, - ); + return noStoreResponse({ ok: true, ignored: true, action }); } // Ignore draft PRs until they become ready_for_review if (payload.pull_request?.draft && action !== "ready_for_review") { - return NextResponse.json( - { ok: true, ignored: true, reason: "draft" }, - { status: 200 }, - ); + return noStoreResponse({ ok: true, ignored: true, reason: "draft" }); } // Avoid replying to bots (including ourselves) if (payload.sender?.type === "Bot") { - return NextResponse.json( - { ok: true, ignored: true, reason: "bot" }, - { status: 200 }, - ); + return noStoreResponse({ ok: true, ignored: true, reason: "bot" }); } const owner = payload.repository?.owner?.login; @@ -97,20 +92,14 @@ export async function POST(request: NextRequest) { const installationId = payload.installation?.id; if (!owner || !repo || !number || !installationId) { - return NextResponse.json( - { - error: "Missing required fields", - details: { owner, repo, number, installationId }, - }, - { status: 400 }, - ); + return noStoreResponse({ error: "Missing required fields" }, 400); } try { const repoFullName = `${owner}/${repo}`; // Gate by DB selection: only auto-review repos that users explicitly enabled. - // Hackathon scope: if *any* user enabled this repo, we process and attach the PR to that GitHubRepo row. + // Verify the installation belongs to a registered user to enforce ownership. const enabledRepo = await prisma.gitHubRepo.findFirst({ where: { repoFullName, @@ -124,10 +113,7 @@ export async function POST(request: NextRequest) { }); if (!enabledRepo) { - return NextResponse.json( - { ok: true, ignored: true, reason: "repo_not_enabled", repoFullName }, - { status: 200 }, - ); + return noStoreResponse({ ok: true, ignored: true, reason: "repo_not_enabled" }); } // Backfill installationId for future lookups. @@ -148,12 +134,9 @@ export async function POST(request: NextRequest) { const pr = await github.getPullRequest(owner, repo, number); const headSha = pr?.head?.sha; if (!headSha) { - return NextResponse.json( - { - error: "Missing head SHA from GitHub PR response", - details: { owner, repo, number }, - }, - { status: 500 }, + return noStoreResponse( + { error: "Missing head SHA from GitHub PR response" }, + 500 ); } @@ -202,17 +185,11 @@ export async function POST(request: NextRequest) { }); } catch (e: any) { if (e?.code === "P2002") { - return NextResponse.json( - { - ok: true, - ignored: true, - reason: "already_reviewed", - repoFullName, - prNumber: number, - headSha, - }, - { status: 200 }, - ); + return noStoreResponse({ + ok: true, + ignored: true, + reason: "already_reviewed", + }); } throw e; } @@ -277,26 +254,21 @@ export async function POST(request: NextRequest) { }, }); - return NextResponse.json( - { - ok: true, - posted: postedUrl, - postError, - stored: { - pullRequestId: prRecord.id, - prReviewId: reviewRow.id, - headSha, - }, + return noStoreResponse({ + ok: true, + posted: postedUrl, + postError, + stored: { + pullRequestId: prRecord.id, + prReviewId: reviewRow.id, + headSha, }, - { status: 200 }, - ); + }); } catch (error: any) { console.error("GitHub webhook PR review error:", error); - return NextResponse.json( - { - error: "Failed to process PR webhook", - }, - { status: 500 }, + return noStoreResponse( + { error: "Failed to process PR webhook" }, + 500 ); } } From 66a83969eae0aa2bf89245b2db56a3cba3543249 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 17:13:19 +0530 Subject: [PATCH 030/123] feat: handle rate limits and retries in analysis jobs - Add rate limit detection utility (lib/utils/rateLimit.ts) to detect 429 errors, extract Retry-After headers, and sanitize error messages - Update markFailed() to accept optional retryAfter parameter for rate-limit-driven delays instead of exponential backoff - Update analysis worker to detect rate limits, extract Retry-After, and pass it to markFailed() for proper scheduling - Sanitize all error logging in worker to prevent token leakage - Surface retryAfter and rateLimited fields in getJob endpoint response so clients can show helpful rate limit messages --- app/api/analysis-jobs/[id]/route.ts | 5 ++ lib/services/analysisJobService.ts | 13 +++++- lib/utils/rateLimit.ts | 71 +++++++++++++++++++++++++++++ scripts/analysisWorker.ts | 26 +++++++++-- 4 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 lib/utils/rateLimit.ts diff --git a/app/api/analysis-jobs/[id]/route.ts b/app/api/analysis-jobs/[id]/route.ts index e1e4cf9e..b561ebd4 100644 --- a/app/api/analysis-jobs/[id]/route.ts +++ b/app/api/analysis-jobs/[id]/route.ts @@ -28,6 +28,9 @@ export async function GET( return NextResponse.json({ error: "Job not found" }, { status: 404 }); } + const details = job.progressDetails as { retryAfter?: number; rateLimited?: boolean } | null; + const retryAfter = details?.retryAfter ?? null; + return NextResponse.json({ job: { id: job.id, @@ -45,6 +48,8 @@ export async function GET( error: job.error, updatedAt: job.updatedAt, createdAt: job.createdAt, + retryAfter, + rateLimited: details?.rateLimited ?? false, }, }); } catch (error: any) { diff --git a/lib/services/analysisJobService.ts b/lib/services/analysisJobService.ts index 41536298..3778951a 100644 --- a/lib/services/analysisJobService.ts +++ b/lib/services/analysisJobService.ts @@ -94,18 +94,27 @@ export class AnalysisJobService { error: string; attempts: number; maxAttempts: number; + retryAfter?: number; }): Promise { const shouldRetry = params.attempts < params.maxAttempts; if (shouldRetry) { - const delay = computeBackoffMs(params.attempts); + const delay = params.retryAfter + ? params.retryAfter * 1000 + : computeBackoffMs(params.attempts); + const retryLabel = params.retryAfter + ? `Rate limited, retrying` + : `Retrying`; await prisma.analysisJob.update({ where: { id: params.jobId }, data: { status: "QUEUED", nextRunAt: new Date(Date.now() + delay), - progressMessage: `Retrying in ${Math.round(delay / 1000)}s`, + progressMessage: `${retryLabel} in ${Math.round(delay / 1000)}s`, error: params.error, + ...(params.retryAfter + ? { progressDetails: { retryAfter: params.retryAfter, rateLimited: true } } + : {}), lockedAt: null, lockedBy: null, lockExpiresAt: null, diff --git a/lib/utils/rateLimit.ts b/lib/utils/rateLimit.ts new file mode 100644 index 00000000..178f8742 --- /dev/null +++ b/lib/utils/rateLimit.ts @@ -0,0 +1,71 @@ +import { AxiosError } from "axios"; + +const RATE_LIMIT_PATTERNS = [ + "rate limit", + "api rate limit", + "secondary rate limit", + "abuse limit", + "too many requests", + "429", +] as const; + +export function isRateLimitError(err: unknown): boolean { + if (err instanceof AxiosError) { + if (err.response?.status === 429) return true; + + const retryAfter = err.response?.headers?.["retry-after"]; + if (retryAfter != null) return true; + + const xRateLimit = err.response?.headers?.["x-ratelimit-remaining"]; + if (xRateLimit === "0") return true; + } + + if (err instanceof Error) { + const msg = err.message.toLowerCase(); + return RATE_LIMIT_PATTERNS.some((p) => msg.includes(p)); + } + + return false; +} + +export function extractRetryAfter(err: unknown): number | null { + if (!(err instanceof AxiosError) || !err.response) return null; + + const header = err.response.headers?.["retry-after"]; + if (header == null) return null; + + const parsed = parseInt(header, 10); + if (Number.isFinite(parsed) && parsed > 0) return parsed; + + const date = new Date(header); + if (!isNaN(date.getTime())) { + const seconds = Math.max(0, Math.ceil((date.getTime() - Date.now()) / 1000)); + return seconds > 0 ? seconds : null; + } + + return null; +} + +export function sanitizeErrorMessage(err: unknown): string { + if (err instanceof AxiosError) { + const status = err.response?.status; + const message = err.response?.data?.message || err.message; + const safe = stripSensitive(String(message)); + return status ? `[${status}] ${safe}` : safe; + } + + if (err instanceof Error) { + return stripSensitive(err.message); + } + + return stripSensitive(String(err)); +} + +function stripSensitive(text: string): string { + return text + .replace(/Bearer\s+[A-Za-z0-9\-._~+/]+=*/gi, "Bearer [REDACTED]") + .replace(/token[=:]\s*[A-Za-z0-9\-._~+/]+=*/gi, "token=[REDACTED]") + .replace(/gh[pousr]_[A-Za-z0-9_]{36,}/gi, "[REDACTED]") + .replace(/https?:\/\/[^@\s]+:[^@\s]+@/g, "https://[REDACTED]@") + .replace(/Authorization:\s*.+/gi, "Authorization: [REDACTED]"); +} diff --git a/scripts/analysisWorker.ts b/scripts/analysisWorker.ts index 1f7d3b6f..b8736a59 100644 --- a/scripts/analysisWorker.ts +++ b/scripts/analysisWorker.ts @@ -3,6 +3,11 @@ import os from "os"; import prisma from "../lib/prisma"; import { analysisJobService } from "../lib/services/analysisJobService"; import { repositoryService } from "../lib/services/repositoryService"; +import { + isRateLimitError, + extractRetryAfter, + sanitizeErrorMessage, +} from "../lib/utils/rateLimit"; import type { AnalysisJob } from "@prisma/client"; const POLL_INTERVAL_MS = 2000; @@ -79,7 +84,7 @@ async function runJob( workerId: params.workerId, lockMs: params.lockMs, }) - .catch((e) => console.error("heartbeat failed", e)); + .catch((e) => console.error("heartbeat failed", sanitizeErrorMessage(e))); }, params.heartbeatIntervalMs); if (job.type !== "repository_analysis") { @@ -97,15 +102,26 @@ async function runJob( workerId: params.workerId, }); } catch (err: any) { - const message = err?.message ? String(err.message) : String(err); - console.error(`Job ${job.id} failed:`, err); + const rateLimited = isRateLimitError(err); + const retryAfter = rateLimited ? extractRetryAfter(err) : null; + const safeMessage = sanitizeErrorMessage(err); + + if (rateLimited) { + console.error( + `Job ${job.id} rate limited (attempt ${job.attempts}/${job.maxAttempts})` + + (retryAfter ? `, retry after ${retryAfter}s` : "") + ); + } else { + console.error(`Job ${job.id} failed: ${safeMessage}`); + } await analysisJobService.markFailed({ jobId: job.id, workerId: params.workerId, - error: message, + error: safeMessage, attempts: job.attempts, maxAttempts: job.maxAttempts, + retryAfter: retryAfter ?? undefined, }); } finally { if (heartbeatTimer) clearInterval(heartbeatTimer); @@ -164,7 +180,7 @@ export async function startAnalysisWorkerLoop(opts?: { if (opts?.once) return; } catch (e) { - console.error("worker loop error:", e); + console.error("worker loop error:", sanitizeErrorMessage(e)); if (opts?.once) return; await sleep(pollIntervalMs); } From acfe2348c74e5665ca08a067049b648c551a02c3 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:49:32 +0530 Subject: [PATCH 031/123] fix(db): add index on Session for expiry cleanup queries Session: add @@index([expires]) to speed up session cleanup queries that filter by expiry date. NextAuth and custom auth flows periodically delete expired sessions, and this index avoids a full table scan. --- prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 14541342..ec3aa93f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -144,6 +144,7 @@ model Session { user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) + @@index([expires]) @@map("sessions") } From e29f9c73823ec969d147be08258a61b3bec8d94c Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:53:09 +0530 Subject: [PATCH 032/123] fix: validate job ID format in analysis job endpoints Add UUID format validation to both analysis job routes to reject invalid job IDs early with a 400 response instead of returning a misleading 404. - app/api/analysis-jobs/[id]/route.ts: validate jobId is valid UUID - app/api/analysis/[jobId]/route.ts: validate jobId is valid UUID --- app/api/analysis-jobs/[id]/route.ts | 8 ++++++++ app/api/analysis/[jobId]/route.ts | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/app/api/analysis-jobs/[id]/route.ts b/app/api/analysis-jobs/[id]/route.ts index b9bf0263..e1e4cf9e 100644 --- a/app/api/analysis-jobs/[id]/route.ts +++ b/app/api/analysis-jobs/[id]/route.ts @@ -11,6 +11,14 @@ export async function GET( const user = await requireAuth(request); const jobId = params.id; + const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + if (!uuidPattern.test(jobId)) { + return NextResponse.json( + { error: "Invalid job ID format. Expected a UUID" }, + { status: 400 } + ); + } + const job = await analysisJobService.getJob({ jobId, userId: user.userId, diff --git a/app/api/analysis/[jobId]/route.ts b/app/api/analysis/[jobId]/route.ts index 55451a28..75fdcf8b 100644 --- a/app/api/analysis/[jobId]/route.ts +++ b/app/api/analysis/[jobId]/route.ts @@ -14,6 +14,14 @@ export async function GET( return NextResponse.json({ error: "Missing jobId" }, { status: 400 }); } + const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + if (!uuidPattern.test(jobId)) { + return NextResponse.json( + { error: "Invalid job ID format. Expected a UUID" }, + { status: 400 } + ); + } + const job = await analysisJobService.getJob({ jobId, userId: user.userId }); if (!job) { From f640bf5535529bb845e7a724cf08569f273dd902 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 16:59:06 +0530 Subject: [PATCH 033/123] feat(ui): add skeleton loaders for analysis job page Replace simple loading text with a skeleton UI that matches the final layout to reduce layout shift when data loads. - Add Skeleton component in src/components/ui/Skeleton.tsx - Add RepositoryAnalysisSkeleton component that mirrors the page layout - Update RepositoryAnalysis.tsx to use the skeleton during loading --- .../ui/RepositoryAnalysisSkeleton.tsx | 39 +++++++++++++++++++ src/components/ui/Skeleton.tsx | 13 +++++++ src/pages/RepositoryAnalysis.tsx | 5 +-- 3 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 src/components/ui/RepositoryAnalysisSkeleton.tsx create mode 100644 src/components/ui/Skeleton.tsx diff --git a/src/components/ui/RepositoryAnalysisSkeleton.tsx b/src/components/ui/RepositoryAnalysisSkeleton.tsx new file mode 100644 index 00000000..fa9db0d4 --- /dev/null +++ b/src/components/ui/RepositoryAnalysisSkeleton.tsx @@ -0,0 +1,39 @@ +import { Skeleton } from "./Skeleton"; + +export const RepositoryAnalysisSkeleton: React.FC = () => { + return ( +
+ {/* Header skeleton */} +
+ +
+ + +
+ +
+
+ +
+ + {/* Tab navigation skeleton */} +
+
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+
+ + {/* Content skeleton */} +
+ + + + + + +
+
+ ); +}; diff --git a/src/components/ui/Skeleton.tsx b/src/components/ui/Skeleton.tsx new file mode 100644 index 00000000..f30a7552 --- /dev/null +++ b/src/components/ui/Skeleton.tsx @@ -0,0 +1,13 @@ +import { cn } from "@/lib/utils"; + +interface SkeletonProps { + className?: string; +} + +export const Skeleton: React.FC = ({ className }) => { + return ( +
+ ); +}; diff --git a/src/pages/RepositoryAnalysis.tsx b/src/pages/RepositoryAnalysis.tsx index a095c6f6..d9ec717a 100644 --- a/src/pages/RepositoryAnalysis.tsx +++ b/src/pages/RepositoryAnalysis.tsx @@ -27,6 +27,7 @@ import { useRouter } from "next/navigation"; import axios from "axios"; import { useToast } from "@/hooks/use-toast"; import { buildApiUrl } from "@/services/apiConfig"; +import { RepositoryAnalysisSkeleton } from "@/components/ui/RepositoryAnalysisSkeleton"; type TabType = | "overview" @@ -223,9 +224,7 @@ export default function RepositoryAnalysis() {
{loading ? ( -
-

Loading repository...

-
+ ) : !job ? (
From a93a30b9934315e8f73460626e0a5ebdbee00894 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 17:04:38 +0530 Subject: [PATCH 034/123] fix(security): enforce ownership and no-store caching on webhook endpoint - Add Cache-Control: no-store to all webhook responses to prevent caching - Remove internal details from error responses to prevent information leakage - Add ownership verification to repo lookup query - Extract noStoreResponse helper for consistent response handling --- app/api/webhooks/github/route.ts | 100 +++++++++++-------------------- 1 file changed, 36 insertions(+), 64 deletions(-) diff --git a/app/api/webhooks/github/route.ts b/app/api/webhooks/github/route.ts index eba6ba72..5cbfb86a 100644 --- a/app/api/webhooks/github/route.ts +++ b/app/api/webhooks/github/route.ts @@ -36,6 +36,13 @@ function shouldHandlePullRequestAction(action: string | undefined): boolean { ); } +function noStoreResponse(data: unknown, status = 200) { + return NextResponse.json(data, { + status, + headers: { "Cache-Control": "no-store" }, + }); +} + export async function POST(request: NextRequest) { const rawBody = await request.text(); @@ -50,45 +57,33 @@ export async function POST(request: NextRequest) { webhookSecret: secret, }) ) { - return NextResponse.json({ error: "Invalid signature" }, { status: 401 }); + return noStoreResponse({ error: "Invalid signature" }, 401); } if (event !== "pull_request") { - return NextResponse.json( - { ok: true, ignored: true, event }, - { status: 200 }, - ); + return noStoreResponse({ ok: true, ignored: true, event }); } let payload: PullRequestWebhookPayload; try { payload = JSON.parse(rawBody); } catch { - return NextResponse.json({ error: "Invalid JSON" }, { status: 400 }); + return noStoreResponse({ error: "Invalid JSON" }, 400); } const action = payload.action; if (!shouldHandlePullRequestAction(action)) { - return NextResponse.json( - { ok: true, ignored: true, action }, - { status: 200 }, - ); + return noStoreResponse({ ok: true, ignored: true, action }); } // Ignore draft PRs until they become ready_for_review if (payload.pull_request?.draft && action !== "ready_for_review") { - return NextResponse.json( - { ok: true, ignored: true, reason: "draft" }, - { status: 200 }, - ); + return noStoreResponse({ ok: true, ignored: true, reason: "draft" }); } // Avoid replying to bots (including ourselves) if (payload.sender?.type === "Bot") { - return NextResponse.json( - { ok: true, ignored: true, reason: "bot" }, - { status: 200 }, - ); + return noStoreResponse({ ok: true, ignored: true, reason: "bot" }); } const owner = payload.repository?.owner?.login; @@ -97,20 +92,14 @@ export async function POST(request: NextRequest) { const installationId = payload.installation?.id; if (!owner || !repo || !number || !installationId) { - return NextResponse.json( - { - error: "Missing required fields", - details: { owner, repo, number, installationId }, - }, - { status: 400 }, - ); + return noStoreResponse({ error: "Missing required fields" }, 400); } try { const repoFullName = `${owner}/${repo}`; // Gate by DB selection: only auto-review repos that users explicitly enabled. - // Hackathon scope: if *any* user enabled this repo, we process and attach the PR to that GitHubRepo row. + // Verify the installation belongs to a registered user to enforce ownership. const enabledRepo = await prisma.gitHubRepo.findFirst({ where: { repoFullName, @@ -124,10 +113,7 @@ export async function POST(request: NextRequest) { }); if (!enabledRepo) { - return NextResponse.json( - { ok: true, ignored: true, reason: "repo_not_enabled", repoFullName }, - { status: 200 }, - ); + return noStoreResponse({ ok: true, ignored: true, reason: "repo_not_enabled" }); } // Backfill installationId for future lookups. @@ -148,12 +134,9 @@ export async function POST(request: NextRequest) { const pr = await github.getPullRequest(owner, repo, number); const headSha = pr?.head?.sha; if (!headSha) { - return NextResponse.json( - { - error: "Missing head SHA from GitHub PR response", - details: { owner, repo, number }, - }, - { status: 500 }, + return noStoreResponse( + { error: "Missing head SHA from GitHub PR response" }, + 500 ); } @@ -202,17 +185,11 @@ export async function POST(request: NextRequest) { }); } catch (e: any) { if (e?.code === "P2002") { - return NextResponse.json( - { - ok: true, - ignored: true, - reason: "already_reviewed", - repoFullName, - prNumber: number, - headSha, - }, - { status: 200 }, - ); + return noStoreResponse({ + ok: true, + ignored: true, + reason: "already_reviewed", + }); } throw e; } @@ -277,26 +254,21 @@ export async function POST(request: NextRequest) { }, }); - return NextResponse.json( - { - ok: true, - posted: postedUrl, - postError, - stored: { - pullRequestId: prRecord.id, - prReviewId: reviewRow.id, - headSha, - }, + return noStoreResponse({ + ok: true, + posted: postedUrl, + postError, + stored: { + pullRequestId: prRecord.id, + prReviewId: reviewRow.id, + headSha, }, - { status: 200 }, - ); + }); } catch (error: any) { console.error("GitHub webhook PR review error:", error); - return NextResponse.json( - { - error: "Failed to process PR webhook", - }, - { status: 500 }, + return noStoreResponse( + { error: "Failed to process PR webhook" }, + 500 ); } } From 1fa7866fb5c177fd490a90fefce494925b1c3ddd Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 17:13:19 +0530 Subject: [PATCH 035/123] feat: handle rate limits and retries in analysis jobs - Add rate limit detection utility (lib/utils/rateLimit.ts) to detect 429 errors, extract Retry-After headers, and sanitize error messages - Update markFailed() to accept optional retryAfter parameter for rate-limit-driven delays instead of exponential backoff - Update analysis worker to detect rate limits, extract Retry-After, and pass it to markFailed() for proper scheduling - Sanitize all error logging in worker to prevent token leakage - Surface retryAfter and rateLimited fields in getJob endpoint response so clients can show helpful rate limit messages --- app/api/analysis-jobs/[id]/route.ts | 5 ++ lib/services/analysisJobService.ts | 13 +++++- lib/utils/rateLimit.ts | 71 +++++++++++++++++++++++++++++ scripts/analysisWorker.ts | 26 +++++++++-- 4 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 lib/utils/rateLimit.ts diff --git a/app/api/analysis-jobs/[id]/route.ts b/app/api/analysis-jobs/[id]/route.ts index e1e4cf9e..b561ebd4 100644 --- a/app/api/analysis-jobs/[id]/route.ts +++ b/app/api/analysis-jobs/[id]/route.ts @@ -28,6 +28,9 @@ export async function GET( return NextResponse.json({ error: "Job not found" }, { status: 404 }); } + const details = job.progressDetails as { retryAfter?: number; rateLimited?: boolean } | null; + const retryAfter = details?.retryAfter ?? null; + return NextResponse.json({ job: { id: job.id, @@ -45,6 +48,8 @@ export async function GET( error: job.error, updatedAt: job.updatedAt, createdAt: job.createdAt, + retryAfter, + rateLimited: details?.rateLimited ?? false, }, }); } catch (error: any) { diff --git a/lib/services/analysisJobService.ts b/lib/services/analysisJobService.ts index 41536298..3778951a 100644 --- a/lib/services/analysisJobService.ts +++ b/lib/services/analysisJobService.ts @@ -94,18 +94,27 @@ export class AnalysisJobService { error: string; attempts: number; maxAttempts: number; + retryAfter?: number; }): Promise { const shouldRetry = params.attempts < params.maxAttempts; if (shouldRetry) { - const delay = computeBackoffMs(params.attempts); + const delay = params.retryAfter + ? params.retryAfter * 1000 + : computeBackoffMs(params.attempts); + const retryLabel = params.retryAfter + ? `Rate limited, retrying` + : `Retrying`; await prisma.analysisJob.update({ where: { id: params.jobId }, data: { status: "QUEUED", nextRunAt: new Date(Date.now() + delay), - progressMessage: `Retrying in ${Math.round(delay / 1000)}s`, + progressMessage: `${retryLabel} in ${Math.round(delay / 1000)}s`, error: params.error, + ...(params.retryAfter + ? { progressDetails: { retryAfter: params.retryAfter, rateLimited: true } } + : {}), lockedAt: null, lockedBy: null, lockExpiresAt: null, diff --git a/lib/utils/rateLimit.ts b/lib/utils/rateLimit.ts new file mode 100644 index 00000000..178f8742 --- /dev/null +++ b/lib/utils/rateLimit.ts @@ -0,0 +1,71 @@ +import { AxiosError } from "axios"; + +const RATE_LIMIT_PATTERNS = [ + "rate limit", + "api rate limit", + "secondary rate limit", + "abuse limit", + "too many requests", + "429", +] as const; + +export function isRateLimitError(err: unknown): boolean { + if (err instanceof AxiosError) { + if (err.response?.status === 429) return true; + + const retryAfter = err.response?.headers?.["retry-after"]; + if (retryAfter != null) return true; + + const xRateLimit = err.response?.headers?.["x-ratelimit-remaining"]; + if (xRateLimit === "0") return true; + } + + if (err instanceof Error) { + const msg = err.message.toLowerCase(); + return RATE_LIMIT_PATTERNS.some((p) => msg.includes(p)); + } + + return false; +} + +export function extractRetryAfter(err: unknown): number | null { + if (!(err instanceof AxiosError) || !err.response) return null; + + const header = err.response.headers?.["retry-after"]; + if (header == null) return null; + + const parsed = parseInt(header, 10); + if (Number.isFinite(parsed) && parsed > 0) return parsed; + + const date = new Date(header); + if (!isNaN(date.getTime())) { + const seconds = Math.max(0, Math.ceil((date.getTime() - Date.now()) / 1000)); + return seconds > 0 ? seconds : null; + } + + return null; +} + +export function sanitizeErrorMessage(err: unknown): string { + if (err instanceof AxiosError) { + const status = err.response?.status; + const message = err.response?.data?.message || err.message; + const safe = stripSensitive(String(message)); + return status ? `[${status}] ${safe}` : safe; + } + + if (err instanceof Error) { + return stripSensitive(err.message); + } + + return stripSensitive(String(err)); +} + +function stripSensitive(text: string): string { + return text + .replace(/Bearer\s+[A-Za-z0-9\-._~+/]+=*/gi, "Bearer [REDACTED]") + .replace(/token[=:]\s*[A-Za-z0-9\-._~+/]+=*/gi, "token=[REDACTED]") + .replace(/gh[pousr]_[A-Za-z0-9_]{36,}/gi, "[REDACTED]") + .replace(/https?:\/\/[^@\s]+:[^@\s]+@/g, "https://[REDACTED]@") + .replace(/Authorization:\s*.+/gi, "Authorization: [REDACTED]"); +} diff --git a/scripts/analysisWorker.ts b/scripts/analysisWorker.ts index 1f7d3b6f..b8736a59 100644 --- a/scripts/analysisWorker.ts +++ b/scripts/analysisWorker.ts @@ -3,6 +3,11 @@ import os from "os"; import prisma from "../lib/prisma"; import { analysisJobService } from "../lib/services/analysisJobService"; import { repositoryService } from "../lib/services/repositoryService"; +import { + isRateLimitError, + extractRetryAfter, + sanitizeErrorMessage, +} from "../lib/utils/rateLimit"; import type { AnalysisJob } from "@prisma/client"; const POLL_INTERVAL_MS = 2000; @@ -79,7 +84,7 @@ async function runJob( workerId: params.workerId, lockMs: params.lockMs, }) - .catch((e) => console.error("heartbeat failed", e)); + .catch((e) => console.error("heartbeat failed", sanitizeErrorMessage(e))); }, params.heartbeatIntervalMs); if (job.type !== "repository_analysis") { @@ -97,15 +102,26 @@ async function runJob( workerId: params.workerId, }); } catch (err: any) { - const message = err?.message ? String(err.message) : String(err); - console.error(`Job ${job.id} failed:`, err); + const rateLimited = isRateLimitError(err); + const retryAfter = rateLimited ? extractRetryAfter(err) : null; + const safeMessage = sanitizeErrorMessage(err); + + if (rateLimited) { + console.error( + `Job ${job.id} rate limited (attempt ${job.attempts}/${job.maxAttempts})` + + (retryAfter ? `, retry after ${retryAfter}s` : "") + ); + } else { + console.error(`Job ${job.id} failed: ${safeMessage}`); + } await analysisJobService.markFailed({ jobId: job.id, workerId: params.workerId, - error: message, + error: safeMessage, attempts: job.attempts, maxAttempts: job.maxAttempts, + retryAfter: retryAfter ?? undefined, }); } finally { if (heartbeatTimer) clearInterval(heartbeatTimer); @@ -164,7 +180,7 @@ export async function startAnalysisWorkerLoop(opts?: { if (opts?.once) return; } catch (e) { - console.error("worker loop error:", e); + console.error("worker loop error:", sanitizeErrorMessage(e)); if (opts?.once) return; await sleep(pollIntervalMs); } From 0b7d5a62aa784ef273dfc1c6340f8abc49cded40 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 17:19:35 +0530 Subject: [PATCH 036/123] handle rate limits and retries for GitHub integrations Add GitHubRateLimitError handling to integration routes that make API calls: - repositories/route.ts: catch rate limits and return 429 with retryAfter - connect/route.ts: catch rate limits and return 429 with retryAfter - app/sync/route.ts: catch rate limits and return 429 with retryAfter - app/callback/route.ts: catch rate limits and redirect with rate_limit reason --- app/api/integrations/github/app/callback/route.ts | 10 +++++++++- app/api/integrations/github/app/sync/route.ts | 10 +++++++++- app/api/integrations/github/connect/route.ts | 10 +++++++++- app/api/integrations/github/repositories/route.ts | 12 ++++++++++-- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/app/api/integrations/github/app/callback/route.ts b/app/api/integrations/github/app/callback/route.ts index f031a1c7..d8978355 100644 --- a/app/api/integrations/github/app/callback/route.ts +++ b/app/api/integrations/github/app/callback/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import prisma from "@/lib/prisma"; import { GitHubAppService } from "@/lib/services/githubAppService"; -import { GitHubService } from "@/lib/services/githubService"; +import { GitHubService, GitHubRateLimitError } from "@/lib/services/githubService"; import { verifySignedState } from "@/lib/utils/signedState"; type InstallState = { userId: number; ts: number; nonce?: string }; @@ -126,6 +126,14 @@ export async function GET(request: NextRequest) { return NextResponse.redirect(redirectUrl); } catch (e: any) { console.error("GitHub App callback error:", e); + + if (e instanceof GitHubRateLimitError) { + redirectUrl.searchParams.set("install", "error"); + redirectUrl.searchParams.set("reason", "rate_limit"); + redirectUrl.searchParams.set("retryAfter", String(e.retryAfterSeconds)); + return NextResponse.redirect(redirectUrl); + } + redirectUrl.searchParams.set("install", "error"); redirectUrl.searchParams.set("reason", e?.message || "unknown"); return NextResponse.redirect(redirectUrl); diff --git a/app/api/integrations/github/app/sync/route.ts b/app/api/integrations/github/app/sync/route.ts index 7d487764..5e437e02 100644 --- a/app/api/integrations/github/app/sync/route.ts +++ b/app/api/integrations/github/app/sync/route.ts @@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; import { isHttpError, requireAuth } from "@/lib/middleware"; import prisma from "@/lib/prisma"; import { GitHubAppService } from "@/lib/services/githubAppService"; -import { GitHubService } from "@/lib/services/githubService"; +import { GitHubService, GitHubRateLimitError } from "@/lib/services/githubService"; export async function POST(request: NextRequest) { try { @@ -109,6 +109,14 @@ export async function POST(request: NextRequest) { ); } catch (error: any) { console.error("GitHub App sync error:", error); + + if (error instanceof GitHubRateLimitError) { + return NextResponse.json( + { error: error.message, retryAfter: error.retryAfterSeconds }, + { status: 429 } + ); + } + if (isHttpError(error)) { return NextResponse.json( { error: error.message }, diff --git a/app/api/integrations/github/connect/route.ts b/app/api/integrations/github/connect/route.ts index 00fe0545..1495cf98 100644 --- a/app/api/integrations/github/connect/route.ts +++ b/app/api/integrations/github/connect/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import { isHttpError, requireAuth } from "@/lib/middleware"; import prisma from "@/lib/prisma"; -import { GitHubService } from "@/lib/services/githubService"; +import { GitHubService, GitHubRateLimitError } from "@/lib/services/githubService"; import { toJsonSafe } from "@/lib/utils/jsonSafe"; export async function POST(request: NextRequest) { @@ -46,6 +46,14 @@ export async function POST(request: NextRequest) { return NextResponse.json({ account: toJsonSafe(account) }, { status: 200 }); } catch (error: any) { console.error("GitHub connect error:", error); + + if (error instanceof GitHubRateLimitError) { + return NextResponse.json( + { error: error.message, retryAfter: error.retryAfterSeconds }, + { status: 429 } + ); + } + if (isHttpError(error)) { return NextResponse.json( { error: error.message }, diff --git a/app/api/integrations/github/repositories/route.ts b/app/api/integrations/github/repositories/route.ts index bf6c3292..003c285d 100644 --- a/app/api/integrations/github/repositories/route.ts +++ b/app/api/integrations/github/repositories/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import { isHttpError, requireAuth } from "@/lib/middleware"; -import { GitHubService } from "@/lib/services/githubService"; +import { GitHubService, GitHubRateLimitError } from "@/lib/services/githubService"; import prisma from "@/lib/prisma"; export async function POST(request: NextRequest) { @@ -36,7 +36,7 @@ export async function POST(request: NextRequest) { return NextResponse.json( { error: - "No GitHub token or GitHub App repos found in DB. If you installed the app but weren’t redirected back, set the GitHub App Setup URL to /api/integrations/github/app/callback, or use the Sync Installation option in Contribute.", + "No GitHub token or GitHub App repos found in DB. If you installed the app but weren't redirected back, set the GitHub App Setup URL to /api/integrations/github/app/callback, or use the Sync Installation option in Contribute.", }, { status: 400 }, ); @@ -55,6 +55,14 @@ export async function POST(request: NextRequest) { return NextResponse.json({ repositories, source: "github-app-db" }); } catch (error: any) { console.error("GitHub repositories error:", error); + + if (error instanceof GitHubRateLimitError) { + return NextResponse.json( + { error: error.message, retryAfter: error.retryAfterSeconds }, + { status: 429 } + ); + } + if (isHttpError(error)) { return NextResponse.json( { error: error.message }, From 076805f545077bf41aac9bb7f077a82f82ccd600 Mon Sep 17 00:00:00 2001 From: Harshit Bhardwaj Date: Thu, 21 May 2026 17:24:00 +0530 Subject: [PATCH 037/123] fix: improve logout validation handling --- app/api/auth/logout/route.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/app/api/auth/logout/route.ts b/app/api/auth/logout/route.ts index 5c8b9460..8e3527bc 100644 --- a/app/api/auth/logout/route.ts +++ b/app/api/auth/logout/route.ts @@ -5,22 +5,23 @@ export async function POST(request: NextRequest) { try { const contentType = request.headers.get("content-type"); - if (!contentType || !contentType.includes("application/json")) { + if (contentType && !contentType.includes("application/json")) { return NextResponse.json( { error: "Content-Type must be application/json"}, { status: 400 } ); } - try { - await request.json(); - } catch { - return NextResponse.json( - { error: "Invalid JSON payload"}, - { status: 400 } - ); - } - + if (contentType) { + try { + await request.json(); + } catch { + return NextResponse.json( + { error: "Invalid JSON payload" }, + { status: 400 } + ); + } + } //Authentication user const user = await getAuthUser(request); @@ -45,8 +46,8 @@ export async function POST(request: NextRequest) { //prevent stack trace from reaching client return NextResponse.json( - { error: "Something went wrong" }, - { status: 400 } + { error: "Internal server error" }, + { status: 500 } ); } } \ No newline at end of file From 11e80fd18c796f9ce51e0e46a1dc9ff959c90dc9 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Thu, 21 May 2026 17:28:28 +0530 Subject: [PATCH 038/123] add skeleton loaders to auth pages to reduce layout shift - Create reusable Skeleton component with variant support - Add loading state to Login page with skeleton matching form layout - Add loading state to Signup page with skeleton matching form layout - Skeletons show for 400ms on initial page load to prevent CLS --- src/components/ui/Skeleton.tsx | 41 +++ src/components/ui/index.ts | 1 + src/pages/Login.tsx | 310 +++++++++++++--------- src/pages/Signup.tsx | 471 ++++++++++++++++++--------------- 4 files changed, 483 insertions(+), 340 deletions(-) create mode 100644 src/components/ui/Skeleton.tsx diff --git a/src/components/ui/Skeleton.tsx b/src/components/ui/Skeleton.tsx new file mode 100644 index 00000000..449cc85b --- /dev/null +++ b/src/components/ui/Skeleton.tsx @@ -0,0 +1,41 @@ +import React from 'react' + +interface SkeletonProps { + className?: string + variant?: 'text' | 'circular' | 'rectangular' + width?: string | number + height?: string | number + animation?: 'pulse' | 'wave' | false +} + +export const Skeleton: React.FC = ({ + className = '', + variant = 'rectangular', + width, + height, + animation = 'pulse', +}) => { + const baseClasses = 'bg-muted-foreground/10 rounded' + + const variantClasses = { + text: 'h-4 inline-block', + circular: 'rounded-full', + rectangular: '', + }[variant] + + const animationClass = animation === 'pulse' ? 'animate-pulse' : animation === 'wave' ? 'animate-shimmer' : '' + + const style: React.CSSProperties = { + width: typeof width === 'number' ? `${width}px` : width, + height: typeof height === 'number' ? `${height}px` : height, + } + + return ( + + ) +} diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index f0d1b29d..0ad86fc6 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -6,6 +6,7 @@ export { Spinner, LoadingSpinner } from './Spinner' export { Toaster } from './toaster' export { toast } from '@/hooks/use-toast' export { EmptyState } from './EmptyState' +export { Skeleton } from './Skeleton' export { DropdownMenu, DropdownMenuTrigger, diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index f1e1dd33..0d321a7b 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -13,6 +13,7 @@ import { CardHeader, CardContent, toast, + Skeleton, } from "@/components/ui"; import { useAuth } from "@/contexts/AuthContext"; import { signIn } from "next-auth/react"; @@ -25,6 +26,7 @@ export default function Login() { const [password, setPassword] = useState(""); const [isLoading, setIsLoading] = useState(false); const [isGoogleLoading, setIsGoogleLoading] = useState(false); + const [isPageLoading, setIsPageLoading] = useState(true); const RepoGraph = ({ className, @@ -104,6 +106,11 @@ export default function Login() { const from = searchParams?.get("from") || "/dashboard"; + useEffect(() => { + const timer = setTimeout(() => setIsPageLoading(false), 400); + return () => clearTimeout(timer); + }, []); + useEffect(() => { const error = searchParams?.get("error"); if (!error) return; @@ -235,150 +242,189 @@ export default function Login() { {/* Login Card */} - - -
- -
- - GitVerse - - -

Welcome Back

-

Sign in to your account

-
- - -
-
- -
- - setEmail(e.target.value)} - className="pl-10" - required - /> + {isPageLoading ? ( + +
+
+
+ +
- -
- -
- - setPassword(e.target.value)} - className="pl-10" - required - /> +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+ +
+
+ +
+
- -
- + + ) : ( + <> + - Forgot password? +
+ +
+ + GitVerse + -
+

Welcome Back

+

Sign in to your account

+ - - + +
+
+ +
+ + setEmail(e.target.value)} + className="pl-10" + required + /> +
+
-
-
-
-
-
-
- - Or continue with - +
+ +
+ + setPassword(e.target.value)} + className="pl-10" + required + /> +
+
+ +
+ + + Forgot password? + +
+ + + + +
+
+
+
+
+
+ + Or continue with + +
+
-
-
- + -
- Don't have an account?{" "} - - Sign up - -
- +
+ Don't have an account?{" "} + + Sign up + +
+ + + )}
); diff --git a/src/pages/Signup.tsx b/src/pages/Signup.tsx index 82f91a38..4f235597 100644 --- a/src/pages/Signup.tsx +++ b/src/pages/Signup.tsx @@ -2,7 +2,7 @@ export const dynamic = "force-dynamic"; -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; import { @@ -20,6 +20,7 @@ import { CardHeader, CardContent, toast, + Skeleton, } from "@/components/ui"; import { useAuth } from "@/contexts/AuthContext"; import { signIn } from "next-auth/react"; @@ -30,6 +31,11 @@ export default function Signup() { const { signup } = useAuth(); const repoUrl = searchParams?.get("repoUrl") || ""; + useEffect(() => { + const timer = setTimeout(() => setIsPageLoading(false), 400); + return () => clearTimeout(timer); + }, []); + const RepoGraph = ({ className, style, @@ -113,6 +119,7 @@ export default function Signup() { const [agreedToTerms, setAgreedToTerms] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isGoogleLoading, setIsGoogleLoading] = useState(false); + const [isPageLoading, setIsPageLoading] = useState(true); const handleGoogleSignIn = async () => { setIsGoogleLoading(true); @@ -231,230 +238,278 @@ export default function Signup() { {/* Signup Card */} - - -
- -
- - GitVerse - - -

- Create Account -

-

- Start exploring repositories today -

-
- - -
-
- -
- - setName(e.target.value)} - className="pl-10" - required - /> + {isPageLoading ? ( + +
+
+
+ +
- -
- -
- - setEmail(e.target.value)} - className="pl-10" - required - /> +
+
+ +
-
- -
- -
- - setPassword(e.target.value)} - className="pl-10" - required - /> +
+ +
-

- Must be at least 8 characters -

-
- -
- -
- - setConfirmPassword(e.target.value)} - className="pl-10" - required - /> +
+ + + +
+
+ + +
+
+ + +
+ +
+ +
+ +
+
+ +
+
- - {repoUrl && ( -
+ ) : ( + <> + + -
- -
-

- Repository Ready -

-

- {repoUrl} -

+
+ +
+ + GitVerse + + +

+ Create Account +

+

+ Start exploring repositories today +

+ + + + +
+ +
+ + setName(e.target.value)} + className="pl-10" + required + />
-
- )} - +
+ + setEmail(e.target.value)} + className="pl-10" + required + /> +
+
+ +
- Privacy Policy - - - + +
+ + setPassword(e.target.value)} + className="pl-10" + required + /> +
+

+ Must be at least 8 characters +

+
+ +
+ +
+ + setConfirmPassword(e.target.value)} + className="pl-10" + required + /> +
+
- - + {repoUrl && ( +
+
+ +
+

+ Repository Ready +

+

+ {repoUrl} +

+
+
+
+ )} -
-
-
-
-
-
- - Or continue with - + + + + + +
+
+
+
+
+
+ + Or continue with + +
+
-
-
- + -
- Already have an account?{" "} - - Sign in - -
- +
+ Already have an account?{" "} + + Sign in + +
+ + + )}
); From 0d0241b302a6d31cb33da57dd5511bab910e6faf Mon Sep 17 00:00:00 2001 From: SuhaniSnehasini Date: Thu, 21 May 2026 17:33:27 +0530 Subject: [PATCH 039/123] Add skeleton UI for loading states --- package.json | 1 - src/components/ui/Skeleton.tsx | 12 +++++ src/components/ui/index.ts | 1 + src/pages/Dashboard.tsx | 91 ++++++++++++++++++++++---------- src/pages/RepositoryAnalysis.tsx | 31 ++++++++++- src/pages/SearchPage.tsx | 45 ++++++++++++++-- src/pages/Workers.tsx | 47 ++++++++++++++--- tsconfig.json | 2 +- 8 files changed, 188 insertions(+), 42 deletions(-) create mode 100644 src/components/ui/Skeleton.tsx diff --git a/package.json b/package.json index 030b970c..1d357cbe 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ }, "scripts": { "dev": "NODE_OPTIONS=\"--dns-result-order=ipv4first --no-network-family-autoselection\" next dev", - "build": "prisma generate && next build", "typecheck": "prisma generate && rm -f tsconfig.tsbuildinfo && tsc -p tsconfig.json --noEmit", "build:worker": "tsc -p tsconfig.worker.json", "preworker": "node -e \"const fs=require('fs'); const cp=require('child_process'); if(fs.existsSync('node_modules/.bin/tsc')){cp.execSync('npm run -s build:worker',{stdio:'inherit'});} else {console.log('Skipping build:worker (tsc not installed)');}\"", diff --git a/src/components/ui/Skeleton.tsx b/src/components/ui/Skeleton.tsx new file mode 100644 index 00000000..a9e1c115 --- /dev/null +++ b/src/components/ui/Skeleton.tsx @@ -0,0 +1,12 @@ +import { cn } from "@/lib/utils"; + +interface SkeletonProps extends React.HTMLAttributes {} + +export function Skeleton({ className, ...props }: SkeletonProps) { + return ( +
+ ); +} diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index f0d1b29d..9e0589da 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -23,3 +23,4 @@ export { DropdownMenuSubTrigger, DropdownMenuRadioGroup, } from './dropdown-menu' +export { Skeleton } from './Skeleton' diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 3f162a79..e15658c3 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -24,6 +24,7 @@ import { Button, Input, EmptyState, + Skeleton, } from "@/components/ui"; import { useAuth } from "@/contexts/AuthContext"; import { buildApiUrl } from "@/services/apiConfig"; @@ -232,33 +233,48 @@ export default function Dashboard() { {/* Stats Grid */}
- {stats.map((stat, index) => ( - - -
-
-

- {stat.label} -

-

- {stat.value} -

-

- - {stat.change} -

-
-
- -
-
-
-
- ))} + {loading + ? Array.from({ length: 4 }).map((_, i) => ( + + +
+
+ + + +
+ +
+
+
+ )) + : stats.map((stat, index) => ( + + +
+
+

+ {stat.label} +

+

+ {stat.value} +

+

+ + {stat.change} +

+
+
+ +
+
+
+
+ ))}
@@ -287,8 +303,25 @@ export default function Dashboard() { {loading ? ( -
- Loading repositories... +
+ {Array.from({ length: 3 }).map((_, i) => ( +
+
+ +
+ + +
+
+
+ + +
+
+ ))}
) : recentRepositories.length === 0 ? (
{loading ? ( -
-

Loading repository...

+
+ {/* Header Skeleton */} +
+ +
+ + +
+ +
+ + {/* Tabs Skeleton */} +
+
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+
+ + {/* Content Skeleton */} +
+ +
+ + +
+
) : !job ? (
diff --git a/src/pages/SearchPage.tsx b/src/pages/SearchPage.tsx index a7cd9d0a..76783def 100644 --- a/src/pages/SearchPage.tsx +++ b/src/pages/SearchPage.tsx @@ -15,6 +15,7 @@ import { Button, Input, EmptyState, + Skeleton, } from "@/components/ui"; import { buildApiUrl } from "@/services/apiConfig"; import axios from "axios"; @@ -150,9 +151,47 @@ export default function SearchPage() { {/* Repository Grid/List */} {loading ? ( -
- Loading repositories... -
+ viewMode === "grid" ? ( +
+ {Array.from({ length: 3 }).map((_, i) => ( + + +
+ +
+ + +
+
+
+ + + +
+ + +
+
+
+ ))} +
+ ) : ( +
+ {Array.from({ length: 3 }).map((_, i) => ( + + +
+ +
+ + +
+
+
+
+ ))} +
+ ) ) : sortedRepositories.length === 0 ? ( searchQuery ? ( ([]); + const [loading, setLoading] = useState(true); + + + useEffect(() => { + // Simulate API fetch + const timer = setTimeout(() => { + setWorkers([]); + setLoading(false); + }, 1500); + return () => clearTimeout(timer); + }, []); return ( @@ -38,7 +48,32 @@ export default function Workers() { {/* Content */} - {workers.length === 0 ? ( + {loading ? ( +
+ {Array.from({ length: 3 }).map((_, i) => ( +
+
+ +
+ + +
+
+
+
+ + +
+
+ + +
+ +
+
+ ))} +
+ ) : workers.length === 0 ? ( Date: Thu, 21 May 2026 17:42:14 +0530 Subject: [PATCH 040/123] fix: allow empty json logout requests --- app/api/auth/logout/route.ts | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/app/api/auth/logout/route.ts b/app/api/auth/logout/route.ts index 8e3527bc..9ba46a91 100644 --- a/app/api/auth/logout/route.ts +++ b/app/api/auth/logout/route.ts @@ -12,16 +12,27 @@ export async function POST(request: NextRequest) { ); } - if (contentType) { - try { - await request.json(); - } catch { - return NextResponse.json( - { error: "Invalid JSON payload" }, - { status: 400 } - ); - } - } +const contentLength = request.headers.get("content-length"); + +if ( + contentType && + contentType.includes("application/json") && + contentLength !== "0" +) { + const rawBody = await request.text(); + + // Only validate JSON when body actually exists + if (rawBody.trim().length > 0) { + try { + JSON.parse(rawBody); + } catch { + return NextResponse.json( + { error: "Invalid JSON payload" }, + { status: 400 } + ); + } + } +} //Authentication user const user = await getAuthUser(request); From d0a6ec54449320e74c7d742e599d1296ae2cb2c4 Mon Sep 17 00:00:00 2001 From: Althaf dudekula Date: Thu, 21 May 2026 17:45:19 +0530 Subject: [PATCH 041/123] fix: tighten README sanitization schema and update test script --- package-lock.json | 41 ++++++++++ package.json | 1 + .../repository/RepositoryOverview.tsx | 19 ++++- test-sanitize.mjs | 82 +++++++++++++++++++ 4 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 test-sanitize.mjs diff --git a/package-lock.json b/package-lock.json index 015d421e..82e921f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ "eslint-config-next": "^14.1.0", "postcss": "^8.4.32", "prettier": "^3.1.1", + "rehype-stringify": "^10.0.1", "tailwindcss": "^3.4.0", "tailwindcss-animate": "^1.0.7", "tsx": "^4.21.0", @@ -7005,6 +7006,30 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", @@ -10642,6 +10667,22 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-gfm": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", diff --git a/package.json b/package.json index 030b970c..fc2bffee 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "eslint-config-next": "^14.1.0", "postcss": "^8.4.32", "prettier": "^3.1.1", + "rehype-stringify": "^10.0.1", "tailwindcss": "^3.4.0", "tailwindcss-animate": "^1.0.7", "tsx": "^4.21.0", diff --git a/src/components/repository/RepositoryOverview.tsx b/src/components/repository/RepositoryOverview.tsx index 7ff2dc7f..f8414bdf 100644 --- a/src/components/repository/RepositoryOverview.tsx +++ b/src/components/repository/RepositoryOverview.tsx @@ -187,14 +187,23 @@ export const RepositoryOverview = ({ }; const readmeSanitizeSchema = (() => { - // Allow common README HTML (like ) while keeping things safe. + // Refined schema for sanitizing README markdown + // Allows common README HTML (like alignment/size attributes for images, collapsible details, etc.) + // while preventing XSS, protocol-based exploits, and DOM clobbering. const schema: any = { ...(defaultSchema as any), - tagNames: Array.from( - new Set([...(defaultSchema as any).tagNames, "img"]), - ), + + // Note: "img" is already in defaultSchema.tagNames, so we don't need to add it manually. + + // Explicitly restrict protocols for attributes to mitigate protocol-based XSS (e.g., javascript:) + protocols: { + ...((defaultSchema as any).protocols || {}), + href: ["http", "https", "mailto"], // Only allow safe URI schemes for links + }, + attributes: { ...((defaultSchema as any).attributes || {}), + // Allow target and rel attributes for links (essential for external links) a: Array.from( new Set([ ...(((defaultSchema as any).attributes?.a as any[]) || []), @@ -202,6 +211,7 @@ export const RepositoryOverview = ({ "rel", ]), ), + // Allow standard image attributes used in READMEs (alignment, sizing, and lazy loading) img: ["src", "alt", "title", "width", "height", "align", "loading"], }, }; @@ -209,6 +219,7 @@ export const RepositoryOverview = ({ return schema; })(); + const formatTimeAgo = (timestamp: string) => { const date = new Date(timestamp); const now = new Date(); diff --git a/test-sanitize.mjs b/test-sanitize.mjs new file mode 100644 index 00000000..c87af40e --- /dev/null +++ b/test-sanitize.mjs @@ -0,0 +1,82 @@ +import { defaultSchema } from 'rehype-sanitize'; +import { unified } from 'unified'; +import remarkParse from 'remark-parse'; +import remarkRehype from 'remark-rehype'; +import rehypeRaw from 'rehype-raw'; +import rehypeSanitize from 'rehype-sanitize'; +import rehypeStringify from 'rehype-stringify'; +import remarkGfm from 'remark-gfm'; + +// Refined schema for sanitizing README markdown +const readmeSanitizeSchema = (() => { + const schema = { + ...defaultSchema, + // Note: "img" is already included in defaultSchema.tagNames, so we don't need to add it again. + + // Explicitly restrict protocols for attributes to mitigate protocol-based XSS (e.g. javascript:) + protocols: { + ...(defaultSchema.protocols || {}), + href: ["http", "https", "mailto"], // Only allow safe link protocols + }, + + attributes: { + ...(defaultSchema.attributes || {}), + // Allow target and rel attributes for links (for external links) + a: Array.from( + new Set([ + ...((defaultSchema.attributes?.a) || []), + "target", + "rel", + ]), + ), + // Allow standard image attributes used in READMEs (e.g. alignment and lazy loading) + img: ["src", "alt", "title", "width", "height", "align", "loading"], + }, + }; + return schema; +})(); + + +const processor = unified() + .use(remarkParse) + .use(remarkRehype, { allowDangerousHtml: true }) + .use(rehypeRaw) + .use(rehypeSanitize, readmeSanitizeSchema) + .use(rehypeStringify); + +const maliciousMarkdown = ` +# Comprehensive HTML/Markdown Sanitization Test + +## COLLAPSIBLE DETAILS +
+ Click to view details + Here is some hidden content. +
+ +## TABLES +| Header 1 | Header 2 | +|---|---| +| Cell 1 | Cell 2 | + +## ALIGNMENT & DECORATION +

Centered text

+
Injected Style (should be stripped)
+ +## DANGEROUS HTML TAGS + + + + + + + +## DANGEROUS ATTRIBUTES +Test Image +Malicious Link +
DOM Clobbering / Class Test
+`; + +processor.process(maliciousMarkdown).then((file) => { + console.log("--- Sanitize Output ---"); + console.log(String(file)); +}).catch(console.error); From 39f344e71cd95817a9281d5d6aaeeb6251aa4185 Mon Sep 17 00:00:00 2001 From: Anchal Gupta Date: Thu, 21 May 2026 17:48:00 +0530 Subject: [PATCH 042/123] feat(ui): improve empty state UX --- src/components/ui/EmptyState.tsx | 15 +++++++++------ src/pages/SearchPage.tsx | 6 +++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/components/ui/EmptyState.tsx b/src/components/ui/EmptyState.tsx index 113826f7..5a84ad10 100644 --- a/src/components/ui/EmptyState.tsx +++ b/src/components/ui/EmptyState.tsx @@ -18,18 +18,21 @@ export function EmptyState({ onAction, }: EmptyStateProps) { return ( -
-
-
- {historyError && ( + {historyLoading ? ( +
+ +
+ {Array.from({ length: 3 }).map((_, i) => ( +
+
+ + +
+
+ +
+
+ ))} +
+
+ ) : historyError ? (
{historyError}
- )} - - {historyRepos.length > 0 && ( + ) : historyRepos.length > 0 ? (
{historyRepos.map((repo) => (
@@ -627,6 +655,15 @@ export default function Contribute() {
))}
+ ) : ( + historyLoaded && ( +
+

No stored webhook reviews found for this repository.

+

+ Reviews will appear here automatically when PR webhooks are triggered. +

+
+ ) )} From 4d59a2d276e6c243d3921aa3d10131402a5a5987 Mon Sep 17 00:00:00 2001 From: Kaniska Raj Date: Thu, 21 May 2026 21:54:55 +0530 Subject: [PATCH 052/123] docs: fix markdown code block language --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 32b9cd9a..74a15f78 100644 --- a/README.md +++ b/README.md @@ -295,7 +295,7 @@ If Google login does not work: - Verify `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET`. - Ensure the OAuth callback URL matches: - ``` + ```text http://localhost:3000/api/auth/callback/google ``` From ccbb0bb231d9c1c95d4c9425390afbf8eaf3005e Mon Sep 17 00:00:00 2001 From: "NIVETHAALAPTOP\\meetn" Date: Thu, 21 May 2026 22:04:42 +0530 Subject: [PATCH 053/123] Optimize worker query performance with composite indexes --- package-lock.json | 2229 +++++++++-------- .../migration.sql | 2 + prisma/schema.prisma | 1 + 3 files changed, 1246 insertions(+), 986 deletions(-) create mode 100644 prisma/migrations/20260521163328_optimize_worker_query_indexes/migration.sql diff --git a/package-lock.json b/package-lock.json index 015d421e..d8df761c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,22 +81,103 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@auth/core": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.34.3.tgz", + "integrity": "sha512-jMjY/S0doZnWYNV90x0jmU3B+UcrsfGYnukxYrRbj0CVvGI/MX3JbHsxSrx2d4mbnXaUsqJmAcDfoQWA6r0lOw==", + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "@panva/hkdf": "^1.1.1", + "@types/cookie": "0.6.0", + "cookie": "0.6.0", + "jose": "^5.1.3", + "oauth4webapi": "^2.10.4", + "preact": "10.11.3", + "preact-render-to-string": "5.2.3" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "nodemailer": "^7" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/@auth/core/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@auth/core/node_modules/jose": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@auth/core/node_modules/preact": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/@auth/core/node_modules/preact-render-to-string": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", + "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/@auth/prisma-adapter": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@auth/prisma-adapter/-/prisma-adapter-2.11.1.tgz", - "integrity": "sha512-Ke7DXP0Fy0Mlmjz/ZJLXwQash2UkA4621xCM0rMtEczr1kppLc/njCbUkHkIQ/PnmILjqSPEKeTjDPsYruvkug==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@auth/prisma-adapter/-/prisma-adapter-2.11.2.tgz", + "integrity": "sha512-GyNEUNtrPgDPs0M4xX6F5i7jTsCKwU6BXV9zutctcoo6K1Ud+juckrmQS11uyNgeWsw6sliextHbU/e+8lsizQ==", "license": "ISC", "dependencies": { - "@auth/core": "0.41.1" + "@auth/core": "0.41.2" }, "peerDependencies": { "@prisma/client": ">=2.26.0 || >=3 || >=4 || >=5 || >=6" } }, "node_modules/@auth/prisma-adapter/node_modules/@auth/core": { - "version": "0.41.1", - "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.1.tgz", - "integrity": "sha512-t9cJ2zNYAdWMacGRMT6+r4xr1uybIdmYa49calBPeTqwgAFPV/88ac9TEvCR85pvATiSPt8VaNf+Gt24JIT/uw==", + "version": "0.41.2", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.2.tgz", + "integrity": "sha512-Hx5MNBxN2fJTbJKGUKAA0wca43D0Akl3TvufY54Gn8lop7F+34vU1zA1pn0vQfIoVuLIrpfc2nkyjwIaPJMW7w==", "license": "ISC", "dependencies": { "@panva/hkdf": "^1.2.1", @@ -123,18 +204,18 @@ } }, "node_modules/@auth/prisma-adapter/node_modules/jose": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", - "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } }, "node_modules/@auth/prisma-adapter/node_modules/oauth4webapi": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.3.tgz", - "integrity": "sha512-pQ5BsX3QRTgnt5HxgHwgunIRaDXBdkT23tf8dfzmtTIL2LTpdmxgbpbBm0VgFWAIDlezQvQCTgnVIUmHupXHxw==", + "version": "3.8.6", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.6.tgz", + "integrity": "sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -150,90 +231,57 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@chevrotain/cst-dts-gen": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", - "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "10.5.0", - "@chevrotain/types": "10.5.0", - "lodash": "4.17.21" - } - }, - "node_modules/@chevrotain/gast": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", - "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "10.5.0", - "lodash": "4.17.21" - } - }, - "node_modules/@chevrotain/types": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", - "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/utils": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", - "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==", - "license": "Apache-2.0" - }, "node_modules/@electric-sql/pglite": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.2.tgz", - "integrity": "sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", + "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", "license": "Apache-2.0" }, "node_modules/@electric-sql/pglite-socket": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.0.6.tgz", - "integrity": "sha512-6RjmgzphIHIBA4NrMGJsjNWK4pu+bCWJlEWlwcxFTVY3WT86dFpKwbZaGWZV6C5Rd7sCk1Z0CI76QEfukLAUXw==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.1.1.tgz", + "integrity": "sha512-p2hoXw3Z3LQHwTeikdZNsFBOvXGqKY2hk51BBw+8NKND8eoH+8LFOtW9Z8CQKmTJ2qqGYu82ipqiyFZOTTXNfw==", "license": "Apache-2.0", "bin": { "pglite-server": "dist/scripts/server.js" }, "peerDependencies": { - "@electric-sql/pglite": "0.3.2" + "@electric-sql/pglite": "0.4.1" } }, "node_modules/@electric-sql/pglite-tools": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.2.7.tgz", - "integrity": "sha512-9dAccClqxx4cZB+Ar9B+FZ5WgxDc/Xvl9DPrTWv+dYTf0YNubLzi4wHHRGRGhrJv15XwnyKcGOZAP1VXSneSUg==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.3.1.tgz", + "integrity": "sha512-C+T3oivmy9bpQvSxVqXA1UDY8cB9Eb9vZHL9zxWwEUfDixbXv4G3r2LjoTdR33LD8aomR3O9ZXEO3XEwr/cUCA==", "license": "Apache-2.0", "peerDependencies": { - "@electric-sql/pglite": "0.3.2" + "@electric-sql/pglite": "0.4.1" } }, "node_modules/@emnapi/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", - "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.1.0", + "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", "optional": true, @@ -242,9 +290,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", "optional": true, @@ -253,9 +301,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", "cpu": [ "ppc64" ], @@ -270,9 +318,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", "cpu": [ "arm" ], @@ -287,9 +335,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", "cpu": [ "arm64" ], @@ -304,9 +352,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", "cpu": [ "x64" ], @@ -321,9 +369,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", "cpu": [ "arm64" ], @@ -338,9 +386,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", "cpu": [ "x64" ], @@ -355,9 +403,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", "cpu": [ "arm64" ], @@ -372,9 +420,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", "cpu": [ "x64" ], @@ -389,9 +437,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", "cpu": [ "arm" ], @@ -406,9 +454,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", "cpu": [ "arm64" ], @@ -423,9 +471,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", "cpu": [ "ia32" ], @@ -440,9 +488,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", "cpu": [ "loong64" ], @@ -457,9 +505,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", "cpu": [ "mips64el" ], @@ -474,9 +522,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", "cpu": [ "ppc64" ], @@ -491,9 +539,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", "cpu": [ "riscv64" ], @@ -508,9 +556,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", "cpu": [ "s390x" ], @@ -525,9 +573,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", "cpu": [ "x64" ], @@ -542,9 +590,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", "cpu": [ "arm64" ], @@ -559,9 +607,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", "cpu": [ "x64" ], @@ -576,9 +624,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", "cpu": [ "arm64" ], @@ -593,9 +641,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", "cpu": [ "x64" ], @@ -610,9 +658,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", "cpu": [ "arm64" ], @@ -627,9 +675,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", "cpu": [ "x64" ], @@ -644,9 +692,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", "cpu": [ "arm64" ], @@ -661,9 +709,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", "cpu": [ "ia32" ], @@ -678,9 +726,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", "cpu": [ "x64" ], @@ -695,9 +743,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -748,9 +796,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -759,9 +807,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -782,31 +830,31 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", - "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.10" + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", - "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.3", - "@floating-ui/utils": "^0.2.10" + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", - "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.4" + "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", @@ -814,9 +862,9 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, "node_modules/@google/generative-ai": { @@ -829,9 +877,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.6.tgz", - "integrity": "sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw==", + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", + "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -857,9 +905,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -868,9 +916,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -906,6 +954,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -923,6 +972,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -932,12 +982,13 @@ } }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -985,60 +1036,40 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@mrleebo/prisma-ast": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@mrleebo/prisma-ast/-/prisma-ast-0.12.1.tgz", - "integrity": "sha512-JwqeCQ1U3fvccttHZq7Tk0m/TMC6WcFAQZdukypW3AzlJYKYTGNVd1ANU2GuhKnv4UQuOFj3oAl0LLG/gxFN1w==", - "license": "MIT", - "dependencies": { - "chevrotain": "^10.5.0", - "lilconfig": "^2.1.0" - }, - "engines": { - "node": ">=16" - } + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" }, "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, "node_modules/@neondatabase/serverless": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-1.0.2.tgz", - "integrity": "sha512-I5sbpSIAHiB+b6UttofhrN/UJXII+4tZPAq1qugzwCwLIL8EZLV7F/JyHUrEIiGgQpEXzpnjlJ+zwcEhheGvCw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-1.1.0.tgz", + "integrity": "sha512-r3ZZhRjEcfEdKIZnoB1RusNgvHuaBRqfCzV4Gi+5A9yUX0S4HTws/ASWqt13wL4y4I+0rqsWGdA2w7EQXHi3+Q==", "license": "MIT", - "dependencies": { - "@types/node": "^22.15.30", - "@types/pg": "^8.8.0" - }, "engines": { "node": ">=19.0.0" } }, - "node_modules/@neondatabase/serverless/node_modules/@types/node": { - "version": "22.19.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", - "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@neondatabase/serverless/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "license": "MIT" - }, "node_modules/@next/env": { "version": "14.2.35", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.35.tgz", @@ -1260,6 +1291,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, "license": "MIT", "optional": true, "engines": { @@ -1267,34 +1299,35 @@ } }, "node_modules/@prisma/adapter-neon": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/adapter-neon/-/adapter-neon-7.2.0.tgz", - "integrity": "sha512-XLTCQ0mDdaOqzBO99jyMOWBofJ5iqsvgWYB7lW+2ZEXvY6888tkeKfpo2VwdKhu1Cj/q4IqHd15eFQzPwbpjJQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-neon/-/adapter-neon-7.8.0.tgz", + "integrity": "sha512-9MoOjRuY53sQNyji87u1NivsK0tIacoUE3PqTxdG95b7ORn1pzwribH2z83j4Vh3eb9nQ/UC+MTfVW7jCFnXlA==", "license": "Apache-2.0", "dependencies": { "@neondatabase/serverless": ">0.6.0 <2", - "@prisma/driver-adapter-utils": "7.2.0", + "@prisma/driver-adapter-utils": "7.8.0", "postgres-array": "3.0.4" } }, "node_modules/@prisma/adapter-pg": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.2.0.tgz", - "integrity": "sha512-euIdQ13cRB2wZ3jPsnDnFhINquo1PYFPCg6yVL8b2rp3EdinQHsX9EDdCtRr489D5uhphcRk463OdQAFlsCr0w==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.8.0.tgz", + "integrity": "sha512-ygb3UkerK3v8MDpXVgCISdRNDozpxh6+JVJgiIGbSr5KBgz10LLf5ejUskPGoXlsIjxsOu6nuy1JVQr2EKGSlg==", "license": "Apache-2.0", "dependencies": { - "@prisma/driver-adapter-utils": "7.2.0", + "@prisma/driver-adapter-utils": "7.8.0", + "@types/pg": "^8.16.0", "pg": "^8.16.3", "postgres-array": "3.0.4" } }, "node_modules/@prisma/client": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.2.0.tgz", - "integrity": "sha512-JdLF8lWZ+LjKGKpBqyAlenxd/kXjd1Abf/xK+6vUA7R7L2Suo6AFTHFRpPSdAKCan9wzdFApsUpSa/F6+t1AtA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.8.0.tgz", + "integrity": "sha512-HFp3Dawv/3sU3JtlPha90IB+48lS7zHiH4LKZPjmcE8YH5P9DOXGPvo8dqOtO7MqLDd1p2hOWMcFlRT1DMblHw==", "license": "Apache-2.0", "dependencies": { - "@prisma/client-runtime-utils": "7.2.0" + "@prisma/client-runtime-utils": "7.8.0" }, "engines": { "node": "^20.19 || ^22.12 || >=24.0" @@ -1313,143 +1346,255 @@ } }, "node_modules/@prisma/client-runtime-utils": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.2.0.tgz", - "integrity": "sha512-dn7oB53v0tqkB0wBdMuTNFNPdEbfICEUe82Tn9FoKAhJCUkDH+fmyEp0ClciGh+9Hp2Tuu2K52kth2MTLstvmA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.8.0.tgz", + "integrity": "sha512-5NQZztQ0oY/ADFkmd9gPuweH5A1/CCY8YQPorLLO0Mu6a87mY5gsnDkzmFmIHs9NFaLnZojzgddFVN4RpKYrdw==", "license": "Apache-2.0" }, "node_modules/@prisma/config": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.2.0.tgz", - "integrity": "sha512-qmvSnfQ6l/srBW1S7RZGfjTQhc44Yl3ldvU6y3pgmuLM+83SBDs6UQVgMtQuMRe9J3gGqB0RF8wER6RlXEr6jQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.8.0.tgz", + "integrity": "sha512-HFESzd9rx2ZQxlK+TL7tu1HPvCqrHiL6LCxYykI2c34mvaUuIVVl3lYuicJD/MNnzgPnyeBEMlK4WTomJCV5jw==", "license": "Apache-2.0", "dependencies": { - "c12": "3.1.0", + "c12": "3.3.4", "deepmerge-ts": "7.1.5", - "effect": "3.18.4", + "effect": "3.20.0", "empathic": "2.0.0" } }, "node_modules/@prisma/debug": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz", - "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.8.0.tgz", + "integrity": "sha512-p+QZReysDUqXC+mk17q9a+Y/qzh4c2KYliDK30buYUyfrGeTGSyfmc0AIrJRhZJrLHhRiJa9Au/J72h3C+szvA==", "license": "Apache-2.0" }, "node_modules/@prisma/dev": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.17.0.tgz", - "integrity": "sha512-6sGebe5jxX+FEsQTpjHLzvOGPn6ypFQprcs3jcuIWv1Xp/5v6P/rjfdvAwTkP2iF6pDx2tCd8vGLNWcsWzImTA==", + "version": "0.24.3", + "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.24.3.tgz", + "integrity": "sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg==", "license": "ISC", "dependencies": { - "@electric-sql/pglite": "0.3.2", - "@electric-sql/pglite-socket": "0.0.6", - "@electric-sql/pglite-tools": "0.2.7", - "@hono/node-server": "1.19.6", - "@mrleebo/prisma-ast": "0.12.1", - "@prisma/get-platform": "6.8.2", - "@prisma/query-plan-executor": "6.18.0", + "@electric-sql/pglite": "0.4.1", + "@electric-sql/pglite-socket": "0.1.1", + "@electric-sql/pglite-tools": "0.3.1", + "@hono/node-server": "1.19.11", + "@prisma/get-platform": "7.2.0", + "@prisma/query-plan-executor": "7.2.0", + "@prisma/streams-local": "0.1.2", "foreground-child": "3.3.1", - "get-port-please": "3.1.2", - "hono": "4.10.6", + "get-port-please": "3.2.0", + "hono": "^4.12.8", "http-status-codes": "2.3.0", "pathe": "2.0.3", "proper-lockfile": "4.1.2", - "remeda": "2.21.3", - "std-env": "3.9.0", + "remeda": "2.33.4", + "std-env": "3.10.0", "valibot": "1.2.0", - "zeptomatch": "2.0.2" + "zeptomatch": "2.1.0" } }, "node_modules/@prisma/driver-adapter-utils": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.2.0.tgz", - "integrity": "sha512-gzrUcbI9VmHS24Uf+0+7DNzdIw7keglJsD5m/MHxQOU68OhGVzlphQRobLiDMn8CHNA2XN8uugwKjudVtnfMVQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.8.0.tgz", + "integrity": "sha512-/Q13o0ZT0rjc1Xk0Q9KhZYwuq2EW/vSbWUBKfgEKkaCuB/Sg6bqnjmTZqC5cD4d6y1vfFAEwBRzfzoSMIVJ55A==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.2.0" + "@prisma/debug": "7.8.0" } }, "node_modules/@prisma/engines": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.2.0.tgz", - "integrity": "sha512-HUeOI/SvCDsHrR9QZn24cxxZcujOjcS3w1oW/XVhnSATAli5SRMOfp/WkG3TtT5rCxDA4xOnlJkW7xkho4nURA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.8.0.tgz", + "integrity": "sha512-jx3rCnNNrt5uzbkKlegtQ2GZHxSlihMCzutgT/BP6UIDF1r9tDI39hV/0T/cHZgzJ3ELbuQPXlVZy+Y1n0pcgw==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.2.0", - "@prisma/engines-version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3", - "@prisma/fetch-engine": "7.2.0", - "@prisma/get-platform": "7.2.0" + "@prisma/debug": "7.8.0", + "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", + "@prisma/fetch-engine": "7.8.0", + "@prisma/get-platform": "7.8.0" } }, "node_modules/@prisma/engines-version": { - "version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3.tgz", - "integrity": "sha512-KezsjCZDsbjNR7SzIiVlUsn9PnLePI7r5uxABlwL+xoerurZTfgQVbIjvjF2sVr3Uc0ZcsnREw3F84HvbggGdA==", + "version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a.tgz", + "integrity": "sha512-fJPQxCkLgA5EayWaW8eArgCvjJ+N+Kz3VyeNKMEeYiQC4alNkxRKFVAGxv/ZUzuJISKqdw+zGeDbS6mn6RCPOA==", "license": "Apache-2.0" }, "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", - "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz", + "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.2.0" + "@prisma/debug": "7.8.0" } }, "node_modules/@prisma/fetch-engine": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.2.0.tgz", - "integrity": "sha512-Z5XZztJ8Ap+wovpjPD2lQKnB8nWFGNouCrglaNFjxIWAGWz0oeHXwUJRiclIoSSXN/ptcs9/behptSk8d0Yy6w==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.8.0.tgz", + "integrity": "sha512-gwB0Euiz/DDRyxFRpLXYlK3RfaZUj1c5dAYMuhZYfApg7arknJlcb9bIsOHDppJmbqYaVA+yBIiFMDBfprsNPQ==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.2.0", - "@prisma/engines-version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3", - "@prisma/get-platform": "7.2.0" + "@prisma/debug": "7.8.0", + "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", + "@prisma/get-platform": "7.8.0" } }, "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", - "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz", + "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.2.0" + "@prisma/debug": "7.8.0" } }, "node_modules/@prisma/get-platform": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.8.2.tgz", - "integrity": "sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", + "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.8.2" + "@prisma/debug": "7.2.0" } }, "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.8.2.tgz", - "integrity": "sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz", + "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==", "license": "Apache-2.0" }, "node_modules/@prisma/query-plan-executor": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-6.18.0.tgz", - "integrity": "sha512-jZ8cfzFgL0jReE1R10gT8JLHtQxjWYLiQ//wHmVYZ2rVkFHoh0DT8IXsxcKcFlfKN7ak7k6j0XMNn2xVNyr5cA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz", + "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==", "license": "Apache-2.0" }, + "node_modules/@prisma/streams-local": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@prisma/streams-local/-/streams-local-0.1.2.tgz", + "integrity": "sha512-l49yTxKKF2odFxaAXTmwmkBKL3+bVQ1tFOooGifu4xkdb9NMNLxHj27XAhTylWZod8I+ISGM5erU1xcl/oBCtg==", + "license": "Apache-2.0", + "dependencies": { + "ajv": "^8.12.0", + "better-result": "^2.7.0", + "env-paths": "^3.0.0", + "proper-lockfile": "^4.1.2" + }, + "engines": { + "bun": ">=1.3.6", + "node": ">=22.0.0" + } + }, + "node_modules/@prisma/streams-local/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@prisma/streams-local/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/@prisma/studio-core": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.9.0.tgz", - "integrity": "sha512-xA2zoR/ADu/NCSQuriBKTh6Ps4XjU0bErkEcgMfnSGh346K1VI7iWKnoq1l2DoxUqiddPHIEWwtxJ6xCHG6W7g==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.27.3.tgz", + "integrity": "sha512-AADjNFPdsrglxHQVTmHFqv6DuKQZ5WY4p5/gVFY017twvNrSwpLJ9lqUbYYxEu2W7nbvVxTZA8deJ8LseNALsw==", "license": "Apache-2.0", + "dependencies": { + "@radix-ui/react-toggle": "1.1.10", + "chart.js": "4.5.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0", + "pnpm": "8" + }, "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, + "node_modules/@prisma/studio-core/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@prisma/studio-core/node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@prisma/studio-core/node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", @@ -2713,9 +2858,9 @@ "license": "MIT" }, "node_modules/@reduxjs/toolkit": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", - "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", + "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -2739,9 +2884,9 @@ } }, "node_modules/@reduxjs/toolkit/node_modules/immer": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz", - "integrity": "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==", + "version": "11.1.8", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.8.tgz", + "integrity": "sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==", "license": "MIT", "funding": { "type": "opencollective", @@ -2756,9 +2901,9 @@ "license": "MIT" }, "node_modules/@rushstack/eslint-patch": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.15.0.tgz", - "integrity": "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz", + "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==", "dev": true, "license": "MIT" }, @@ -2791,9 +2936,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dev": true, "license": "MIT", "optional": true, @@ -2808,6 +2953,14 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/@types/d3": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", @@ -3016,9 +3169,9 @@ "license": "MIT" }, "node_modules/@types/d3-shape": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", - "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", "license": "MIT", "dependencies": { "@types/d3-path": "*" @@ -3062,18 +3215,18 @@ } }, "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", "dependencies": { "@types/ms": "*" } }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "license": "MIT" }, "node_modules/@types/estree-jsx": { @@ -3141,18 +3294,18 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", - "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": ">=7.24.0 <7.24.7" } }, "node_modules/@types/pg": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.16.0.tgz", - "integrity": "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -3167,9 +3320,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "version": "18.3.29", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.29.tgz", + "integrity": "sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==", "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -3404,15 +3557,15 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", "license": "ISC" }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", "cpu": [ "arm" ], @@ -3424,9 +3577,9 @@ ] }, "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", "cpu": [ "arm64" ], @@ -3438,9 +3591,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", "cpu": [ "arm64" ], @@ -3452,9 +3605,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", "cpu": [ "x64" ], @@ -3466,9 +3619,9 @@ ] }, "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", "cpu": [ "x64" ], @@ -3480,9 +3633,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", "cpu": [ "arm" ], @@ -3494,9 +3647,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", "cpu": [ "arm" ], @@ -3508,9 +3661,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", "cpu": [ "arm64" ], @@ -3522,9 +3675,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", "cpu": [ "arm64" ], @@ -3535,10 +3688,38 @@ "linux" ] }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", "cpu": [ "ppc64" ], @@ -3550,9 +3731,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", "cpu": [ "riscv64" ], @@ -3564,9 +3745,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", "cpu": [ "riscv64" ], @@ -3578,9 +3759,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", "cpu": [ "s390x" ], @@ -3592,9 +3773,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", "cpu": [ "x64" ], @@ -3606,9 +3787,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", "cpu": [ "x64" ], @@ -3619,10 +3800,24 @@ "linux" ] }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", "cpu": [ "wasm32" ], @@ -3630,16 +3825,18 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", "cpu": [ "arm64" ], @@ -3651,9 +3848,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", "cpu": [ "ia32" ], @@ -3665,9 +3862,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", "cpu": [ "x64" ], @@ -3679,9 +3876,9 @@ ] }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -3702,18 +3899,21 @@ } }, "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", + "dependencies": { + "debug": "4" + }, "engines": { - "node": ">= 14" + "node": ">= 6.0.0" } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -3731,6 +3931,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3740,6 +3941,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -4002,9 +4204,9 @@ "license": "MIT" }, "node_modules/autoprefixer": { - "version": "10.4.23", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", - "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", "dev": true, "funding": [ { @@ -4022,8 +4224,8 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001760", + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -4064,9 +4266,9 @@ } }, "node_modules/axe-core": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz", - "integrity": "sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==", + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.4.tgz", + "integrity": "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==", "dev": true, "license": "MPL-2.0", "engines": { @@ -4074,14 +4276,15 @@ } }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" } }, "node_modules/axobject-query": { @@ -4108,6 +4311,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -4131,13 +4335,16 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.11", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", - "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "version": "2.10.31", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.31.tgz", + "integrity": "sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/bcrypt": { @@ -4163,6 +4370,12 @@ "bcrypt": "bin/bcrypt" } }, + "node_modules/better-result": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.9.2.tgz", + "integrity": "sha512-WIFoBPCdnTOdk9inkE1ZRvCZ4P0CpSkAiLlchC65N7n9DcjZ3NhqkBOlafzpOVnO8ixyi37kicmSJ3ENhPZl7Q==", + "license": "MIT" + }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", @@ -4186,9 +4399,10 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4208,9 +4422,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -4228,11 +4442,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -4259,26 +4473,26 @@ } }, "node_modules/c12": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", - "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", - "license": "MIT", - "dependencies": { - "chokidar": "^4.0.3", - "confbox": "^0.2.2", - "defu": "^6.1.4", - "dotenv": "^16.6.1", - "exsolve": "^1.0.7", - "giget": "^2.0.0", - "jiti": "^2.4.2", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", + "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.4", + "defu": "^6.1.6", + "dotenv": "^17.3.1", + "exsolve": "^1.0.8", + "giget": "^3.2.0", + "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", - "perfect-debounce": "^1.0.0", - "pkg-types": "^2.2.0", - "rc9": "^2.1.2" + "perfect-debounce": "^2.1.0", + "pkg-types": "^2.3.0", + "rc9": "^3.0.1" }, "peerDependencies": { - "magicast": "^0.3.5" + "magicast": "*" }, "peerDependenciesMeta": { "magicast": { @@ -4286,16 +4500,28 @@ } } }, + "node_modules/c12/node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" }, "engines": { @@ -4356,9 +4582,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001762", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz", - "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==", + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", "funding": [ { "type": "opencollective", @@ -4442,44 +4668,33 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/chevrotain": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", - "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", - "license": "Apache-2.0", + "node_modules/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "license": "MIT", "dependencies": { - "@chevrotain/cst-dts-gen": "10.5.0", - "@chevrotain/gast": "10.5.0", - "@chevrotain/types": "10.5.0", - "@chevrotain/utils": "10.5.0", - "lodash": "4.17.21", - "regexp-to-ast": "0.5.0" + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" } }, "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "readdirp": "^5.0.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">= 20.19.0" }, "funding": { "url": "https://paulmillr.com/funding/" } }, - "node_modules/citty": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", - "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", - "license": "MIT", - "dependencies": { - "consola": "^3.2.3" - } - }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -4511,6 +4726,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -4523,6 +4739,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -4564,20 +4781,11 @@ "license": "MIT" }, "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", "license": "MIT" }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -4826,9 +5034,9 @@ } }, "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", "license": "ISC", "engines": { "node": ">=12" @@ -5115,9 +5323,9 @@ "license": "MIT" }, "node_modules/decode-named-character-reference": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", - "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "license": "MIT", "dependencies": { "character-entities": "^2.0.0" @@ -5180,15 +5388,15 @@ } }, "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", "license": "MIT" }, "node_modules/delaunator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", - "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", "license": "ISC", "dependencies": { "robust-predicates": "^3.0.2" @@ -5316,6 +5524,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, "license": "MIT" }, "node_modules/ecdsa-sig-formatter": { @@ -5328,9 +5537,9 @@ } }, "node_modules/effect": { - "version": "3.18.4", - "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", - "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz", + "integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==", "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -5338,9 +5547,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.267", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", - "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "version": "1.5.360", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.360.tgz", + "integrity": "sha512-GkcBt6YYAw9SxFWn+xVar4cLVGlXVuswwtRLBozi2zp0GjXs4ZnOrqV4zbXzg35n7w81hCkyJNYicgXlVHAmBA==", "dev": true, "license": "ISC" }, @@ -5348,6 +5557,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, "license": "MIT" }, "node_modules/empathic": { @@ -5371,10 +5581,22 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "dev": true, "license": "MIT", "dependencies": { @@ -5459,16 +5681,16 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", - "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", + "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.24.1", + "es-abstract": "^1.24.2", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", @@ -5480,7 +5702,7 @@ "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", - "safe-array-concat": "^1.1.3" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -5545,9 +5767,9 @@ } }, "node_modules/es-toolkit": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.43.0.tgz", - "integrity": "sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==", + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz", + "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==", "license": "MIT", "workspaces": [ "docs", @@ -5555,9 +5777,9 @@ ] }, "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -5568,32 +5790,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, "node_modules/escalade": { @@ -5740,15 +5962,15 @@ } }, "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", "dev": true, "license": "MIT", "dependencies": { "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" } }, "node_modules/eslint-import-resolver-node/node_modules/debug": { @@ -5824,9 +6046,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -5858,9 +6080,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -5911,9 +6133,9 @@ } }, "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -5922,9 +6144,9 @@ } }, "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -5981,9 +6203,9 @@ } }, "node_modules/eslint-plugin-react/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -6005,9 +6227,9 @@ } }, "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -6017,24 +6239,6 @@ "node": "*" } }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/eslint-plugin-react/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -6076,9 +6280,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -6087,9 +6291,9 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -6118,9 +6322,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6174,9 +6378,9 @@ } }, "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, "node_modules/exsolve": { @@ -6217,7 +6421,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -6264,6 +6467,22 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -6356,16 +6575,16 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", @@ -6519,33 +6738,39 @@ } }, "node_modules/gaxios": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", - "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2", - "rimraf": "^5.0.1" + "node-fetch": "^3.3.2" }, "engines": { "node": ">=18" } }, - "node_modules/gaxios/node_modules/rimraf": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", - "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", - "license": "ISC", + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" + "agent-base": "^7.1.2", + "debug": "4" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">= 14" } }, "node_modules/gcp-metadata": { @@ -6615,9 +6840,9 @@ } }, "node_modules/get-port-please": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.1.2.tgz", - "integrity": "sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz", + "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==", "license": "MIT" }, "node_modules/get-proto": { @@ -6652,9 +6877,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "dev": true, "license": "MIT", "dependencies": { @@ -6665,18 +6890,10 @@ } }, "node_modules/giget": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", - "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-3.2.0.tgz", + "integrity": "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==", "license": "MIT", - "dependencies": { - "citty": "^0.1.6", - "consola": "^3.4.0", - "defu": "^6.1.4", - "node-fetch-native": "^1.6.6", - "nypm": "^0.6.0", - "pathe": "^2.0.3" - }, "bin": { "giget": "dist/cli.mjs" } @@ -6685,6 +6902,8 @@ "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -6771,17 +6990,16 @@ } }, "node_modules/google-auth-library": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", - "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^7.0.0", - "gcp-metadata": "^8.0.0", - "google-logging-utils": "^1.0.0", - "gtoken": "^8.0.0", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", "jws": "^4.0.0" }, "engines": { @@ -6828,18 +7046,11 @@ "dev": true, "license": "MIT" }, - "node_modules/gtoken": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", - "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", - "license": "MIT", - "dependencies": { - "gaxios": "^7.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=18" - } + "node_modules/graphmatch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz", + "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==", + "license": "MIT" }, "node_modules/has-bigints": { "version": "1.1.0", @@ -6921,9 +7132,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -7082,9 +7293,9 @@ } }, "node_modules/hono": { - "version": "4.10.6", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.10.6.tgz", - "integrity": "sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g==", + "version": "4.12.21", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.21.tgz", + "integrity": "sha512-uV63apnb0kyPtAUwoWgaGh9HyIFcv8lgmzPZSiTBQAFOFGIzka5EZ1dZocmGnn0XdX0+XTqJ6Tqv7selMuGLRQ==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -7117,16 +7328,16 @@ "license": "MIT" }, "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", + "agent-base": "6", "debug": "4" }, "engines": { - "node": ">= 14" + "node": ">= 6" } }, "node_modules/iconv-lite": { @@ -7369,13 +7580,13 @@ } }, "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "hasown": "^2.0.3" }, "engines": { "node": ">= 0.4" @@ -7459,6 +7670,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7768,6 +7980,7 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -7783,9 +7996,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" @@ -7966,12 +8179,16 @@ } }, "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lines-and-columns": { @@ -7997,12 +8214,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -8081,15 +8292,21 @@ } }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } }, "node_modules/lru.min": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.3.tgz", - "integrity": "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", "license": "MIT", "engines": { "bun": ">=1.0.0", @@ -8158,9 +8375,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -9023,6 +9240,7 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -9045,10 +9263,11 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -9080,9 +9299,9 @@ } }, "node_modules/mysql2/node_modules/iconv-lite": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", - "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -9120,9 +9339,9 @@ } }, "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", @@ -9211,9 +9430,9 @@ } }, "node_modules/next-auth": { - "version": "4.24.13", - "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.13.tgz", - "integrity": "sha512-sgObCfcfL7BzIK76SS5TnQtc3yo2Oifp/yIpfv6fMfeBOiBJkDWF3A2y9+yqnmJ4JKc2C+nMjSjmgDeTwgN1rQ==", + "version": "4.24.14", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.14.tgz", + "integrity": "sha512-YRz6xFDXKUwiXSMMChbrBEWyFktZ1qZXEgeSHQQ3nsy08B4c/xLk6REeutRsIFwkjY/1+ShHnu07DN3JeJguig==", "license": "ISC", "dependencies": { "@babel/runtime": "^7.20.13", @@ -9271,9 +9490,9 @@ } }, "node_modules/node-addon-api": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", - "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz", + "integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==", "license": "MIT", "engines": { "node": "^18 || ^20 || >= 21" @@ -9299,6 +9518,35 @@ "node": ">=10.5.0" } }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/node-fetch": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", @@ -9317,12 +9565,6 @@ "url": "https://opencollective.com/node-fetch" } }, - "node_modules/node-fetch-native": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", - "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", - "license": "MIT" - }, "node_modules/node-gyp-build": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", @@ -9335,11 +9577,14 @@ } }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.45", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.45.tgz", + "integrity": "sha512-iIbHXV9eBB2nB0wa7oTsrrXq+qQt+9SIlx9AX3T96YgobtEQfis5n6TJ6vV+3QP8DwdriEAcGhARaFCu37peBg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/normalize-path": { "version": "3.0.0", @@ -9351,31 +9596,23 @@ "node": ">=0.10.0" } }, - "node_modules/nypm": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", - "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", - "license": "MIT", - "dependencies": { - "citty": "^0.1.6", - "consola": "^3.4.2", - "pathe": "^2.0.3", - "pkg-types": "^2.3.0", - "tinyexec": "^1.0.1" - }, - "bin": { - "nypm": "dist/cli.mjs" - }, - "engines": { - "node": "^14.16.0 || >=16.10.0" - } - }, "node_modules/oauth": { "version": "0.9.15", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", "license": "MIT" }, + "node_modules/oauth4webapi": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.17.0.tgz", + "integrity": "sha512-lbC0Z7uzAFNFyzEYRIC+pkSVvDHJTbEW+dYlSBAlCYDe6RxUkJ26bClhk8ocBZip1wfI9uKTe0fm4Ib4RHn6uQ==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -9387,10 +9624,9 @@ } }, "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", "license": "MIT", "engines": { "node": ">= 6" @@ -9549,27 +9785,6 @@ "url": "https://github.com/sponsors/panva" } }, - "node_modules/openid-client/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/openid-client/node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -9728,6 +9943,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -9740,6 +9956,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -9757,20 +9980,20 @@ "license": "MIT" }, "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", "license": "MIT" }, "node_modules/pg": { - "version": "8.16.3", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", - "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", + "integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==", "license": "MIT", "dependencies": { - "pg-connection-string": "^2.9.1", - "pg-pool": "^3.10.1", - "pg-protocol": "^1.10.3", + "pg-connection-string": "^2.13.0", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.14.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, @@ -9778,7 +10001,7 @@ "node": ">= 16.0.0" }, "optionalDependencies": { - "pg-cloudflare": "^1.2.7" + "pg-cloudflare": "^1.4.0" }, "peerDependencies": { "pg-native": ">=3.0.1" @@ -9790,16 +10013,16 @@ } }, "node_modules/pg-cloudflare": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", - "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", "license": "MIT", "optional": true }, "node_modules/pg-connection-string": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", - "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz", + "integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==", "license": "MIT" }, "node_modules/pg-int8": { @@ -9812,18 +10035,18 @@ } }, "node_modules/pg-pool": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", - "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", "license": "MIT", "peerDependencies": { "pg": ">=8.0" } }, "node_modules/pg-protocol": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", - "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz", + "integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==", "license": "MIT" }, "node_modules/pg-types": { @@ -9867,9 +10090,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -9900,13 +10123,13 @@ } }, "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", "license": "MIT", "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", + "confbox": "^0.2.4", + "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, @@ -9921,9 +10144,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -9941,7 +10164,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -9967,6 +10190,28 @@ "postcss": "^8.0.0" } }, + "node_modules/postcss-import/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/postcss-js": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", @@ -10036,19 +10281,6 @@ } } }, - "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, "node_modules/postcss-nested": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", @@ -10181,9 +10413,9 @@ } }, "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", "bin": { @@ -10203,16 +10435,16 @@ "license": "MIT" }, "node_modules/prisma": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.2.0.tgz", - "integrity": "sha512-jSdHWgWOgFF24+nRyyNRVBIgGDQEsMEF8KPHvhBBg3jWyR9fUAK0Nq9ThUmiGlNgq2FA7vSk/ZoCvefod+a8qg==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.8.0.tgz", + "integrity": "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/config": "7.2.0", - "@prisma/dev": "0.17.0", - "@prisma/engines": "7.2.0", - "@prisma/studio-core": "0.9.0", + "@prisma/config": "7.8.0", + "@prisma/dev": "0.24.3", + "@prisma/engines": "7.8.0", + "@prisma/studio-core": "0.27.3", "mysql2": "3.15.3", "postgres": "3.4.7" }, @@ -10282,10 +10514,13 @@ } }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/punycode": { "version": "2.3.1", @@ -10335,13 +10570,13 @@ "license": "MIT" }, "node_modules/rc9": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", - "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz", + "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==", "license": "MIT", "dependencies": { - "defu": "^6.1.4", - "destr": "^2.0.3" + "defu": "^6.1.6", + "destr": "^2.0.5" } }, "node_modules/react": { @@ -10370,9 +10605,9 @@ } }, "node_modules/react-is": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz", - "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", + "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", "license": "MIT", "peer": true }, @@ -10404,9 +10639,9 @@ } }, "node_modules/react-redux": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", - "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", + "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", "license": "MIT", "dependencies": { "@types/use-sync-external-store": "^0.0.6", @@ -10506,12 +10741,12 @@ } }, "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", "license": "MIT", "engines": { - "node": ">= 14.18.0" + "node": ">= 20.19.0" }, "funding": { "type": "individual", @@ -10519,15 +10754,15 @@ } }, "node_modules/recharts": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.6.0.tgz", - "integrity": "sha512-L5bjxvQRAe26RlToBAziKUB7whaGKEwD3znoM6fz3DrTowCIC/FnJYnuq1GEzB8Zv2kdTfaxQfi5GoH0tBinyg==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz", + "integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==", "license": "MIT", "workspaces": [ "www" ], "dependencies": { - "@reduxjs/toolkit": "1.x.x || 2.x.x", + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", @@ -10586,12 +10821,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexp-to-ast": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", - "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", - "license": "MIT" - }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -10709,24 +10938,21 @@ } }, "node_modules/remeda": { - "version": "2.21.3", - "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.21.3.tgz", - "integrity": "sha512-XXrZdLA10oEOQhLLzEJEiFFSKi21REGAkHdImIb4rt/XXy8ORGXh5HCcpUOsElfPNDb+X6TA/+wkh+p2KffYmg==", + "version": "2.33.4", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz", + "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==", "license": "MIT", - "dependencies": { - "type-fest": "^4.39.1" + "funding": { + "url": "https://github.com/sponsors/remeda" } }, - "node_modules/remeda/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "license": "(MIT OR CC0-1.0)", + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, "node_modules/reselect": { @@ -10736,13 +10962,16 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.1", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -10814,9 +11043,9 @@ } }, "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -10828,7 +11057,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -10847,9 +11076,9 @@ } }, "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -10860,9 +11089,9 @@ } }, "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", "license": "Unlicense" }, "node_modules/run-parallel": { @@ -10896,15 +11125,15 @@ "license": "BSD-3-Clause" }, "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, @@ -10986,9 +11215,9 @@ } }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -11093,14 +11322,14 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -11225,9 +11454,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "license": "MIT" }, "node_modules/stop-iteration-iterator": { @@ -11256,6 +11485,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -11274,6 +11504,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -11288,12 +11519,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -11303,12 +11536,13 @@ } }, "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -11448,6 +11682,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -11461,6 +11696,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -11593,9 +11829,9 @@ } }, "node_modules/tailwind-merge": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", - "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz", + "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==", "license": "MIT", "funding": { "type": "github", @@ -11698,17 +11934,14 @@ "jiti": "bin/jiti.js" } }, - "node_modules/tailwindcss/node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "node_modules/tailwindcss/node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "dev": true, "license": "MIT", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" + "node": ">= 6" } }, "node_modules/tailwindcss/node_modules/readdirp": { @@ -11724,6 +11957,28 @@ "node": ">=8.10.0" } }, + "node_modules/tailwindcss/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -11760,24 +12015,15 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, - "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -11805,9 +12051,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -11890,14 +12136,13 @@ "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==", + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", + "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" + "esbuild": "~0.28.0" }, "bin": { "tsx": "dist/cli.mjs" @@ -12047,9 +12292,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "license": "MIT" }, "node_modules/unified": { @@ -12111,9 +12356,9 @@ } }, "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -12140,38 +12385,41 @@ } }, "node_modules/unrs-resolver": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "napi-postinstall": "^0.3.0" + "napi-postinstall": "^0.3.4" }, "funding": { "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" } }, "node_modules/update-browserslist-db": { @@ -12278,6 +12526,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", "license": "MIT", "bin": { "uuid": "dist/bin/uuid" @@ -12463,9 +12712,9 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", "dev": true, "license": "MIT", "dependencies": { @@ -12498,6 +12747,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -12516,6 +12766,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -12533,12 +12784,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -12553,6 +12806,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -12565,6 +12819,7 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -12574,12 +12829,13 @@ } }, "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -12596,9 +12852,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -12645,12 +12901,13 @@ } }, "node_modules/zeptomatch": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.0.2.tgz", - "integrity": "sha512-H33jtSKf8Ijtb5BW6wua3G5DhnFjbFML36eFu+VdOoVY4HD9e7ggjqdM6639B+L87rjnR6Y+XeRzBXZdy52B/g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz", + "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==", "license": "MIT", "dependencies": { - "grammex": "^3.1.10" + "grammex": "^3.1.11", + "graphmatch": "^1.1.0" } }, "node_modules/zwitch": { diff --git a/prisma/migrations/20260521163328_optimize_worker_query_indexes/migration.sql b/prisma/migrations/20260521163328_optimize_worker_query_indexes/migration.sql new file mode 100644 index 00000000..2ec002d8 --- /dev/null +++ b/prisma/migrations/20260521163328_optimize_worker_query_indexes/migration.sql @@ -0,0 +1,2 @@ +-- CreateIndex +CREATE INDEX "analysis_jobs_status_lock_expires_at_next_run_at_idx" ON "analysis_jobs"("status", "lock_expires_at", "next_run_at"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9aeb0a7e..1403c854 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -220,6 +220,7 @@ model AnalysisJob { @@index([lockExpiresAt]) @@index([repositoryId]) @@index([userId]) + @@index([status, lockExpiresAt, nextRunAt]) @@map("analysis_jobs") } From ec3ac2217c2d771da073e42e507a881f11c7b0f6 Mon Sep 17 00:00:00 2001 From: "NIVETHAALAPTOP\\meetn" Date: Thu, 21 May 2026 22:18:13 +0530 Subject: [PATCH 054/123] Optimize worker query performance with composite indexes --- package-lock.json | 24 ++++++++++++++++++++++++ package.json | 1 + 2 files changed, 25 insertions(+) diff --git a/package-lock.json b/package-lock.json index d8df761c..b0624906 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "bcryptjs": "^3.0.3", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", + "cross-env": "^10.1.0", "d3": "^7.9.0", "dotenv": "^16.4.5", "google-auth-library": "^10.3.0", @@ -300,6 +301,12 @@ "tslib": "^2.4.0" } }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.0", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", @@ -4795,6 +4802,23 @@ "node": ">= 0.6" } }, + "node_modules/cross-env": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", + "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", + "license": "MIT", + "dependencies": { + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", diff --git a/package.json b/package.json index 030b970c..064de38e 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "bcryptjs": "^3.0.3", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", + "cross-env": "^10.1.0", "d3": "^7.9.0", "dotenv": "^16.4.5", "google-auth-library": "^10.3.0", From 267d04a508176fd2c52f291f8104f4f73fcc762c Mon Sep 17 00:00:00 2001 From: xyron24 Date: Thu, 21 May 2026 22:21:18 +0530 Subject: [PATCH 055/123] docs: document environment variables and add troubleshooting guide --- README.md | 163 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 147 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 7f4575b6..587905e6 100644 --- a/README.md +++ b/README.md @@ -211,16 +211,24 @@ const loadMore = async () => { ### Vercel (Recommended) -1. Push your code to GitHub -2. Import project in Vercel -3. Add environment variables in Vercel dashboard -4. Deploy! +1. Push your code to GitHub. +2. Import the project in the [Vercel dashboard](https://vercel.com/new). +3. Under **Settings → Environment Variables**, add every variable listed in the [Environment Variables](#-environment-variables) section below. Vercel automatically makes them available at build time and runtime. + - For `NEXTAUTH_URL`, set the value to your Vercel deployment URL (e.g. `https://gitverse.vercel.app`). Leave it unset locally — NextAuth infers it automatically in development. + - Mark sensitive secrets (e.g. `JWT_SECRET`, `NEXTAUTH_SECRET`, `GOOGLE_CLIENT_SECRET`, `GEMINI_API_KEY`) as **Sensitive** in Vercel so they are never exposed in logs. +4. Click **Deploy**. + +> **Tip:** Vercel re-deploys automatically on every push to `main`. If you update an environment variable in the dashboard, trigger a redeploy from **Deployments → Redeploy** for the new value to take effect. ### Docker ```bash docker build -t gitverse-nextjs . -docker run -p 3000:3000 gitverse-nextjs +docker run -p 3000:3000 \ + -e DATABASE_URL="..." \ + -e JWT_SECRET="..." \ + -e GEMINI_API_KEY="..." \ + gitverse-nextjs ``` ### Firebase App Hosting (Cloud Run) @@ -253,22 +261,145 @@ firebase deploy ## 📠Environment Variables -Required: +Copy `.env.example` to `.env.local` and fill in the values before starting the dev server: + +```bash +cp .env.example .env.local +``` + +> **Never commit `.env.local` or any file containing real secrets.** It is already listed in `.gitignore`. + +### Required Variables + +| Variable | Description | How to obtain | +| :--- | :--- | :--- | +| `DATABASE_URL` | PostgreSQL connection string (with SSL) | Create a free database on [Neon](https://neon.tech) → **Connection Details** → copy the connection string. Append `?sslmode=require&schema=public` if not already present. | +| `JWT_SECRET` | Secret used to sign custom JWT tokens | Generate with `openssl rand -base64 32` or any random string ≥ 32 characters. | +| `GEMINI_API_KEY` | Google Gemini API key for AI features | Go to [Google AI Studio](https://aistudio.google.com/app/apikey) → **Create API key**. | + +### OAuth / NextAuth Variables + +| Variable | Description | How to obtain | +| :--- | :--- | :--- | +| `NEXTAUTH_URL` | Canonical base URL of your deployment | Set to `http://localhost:3000` in development. On Vercel, set to your deployment URL (e.g. `https://gitverse.vercel.app`). | +| `NEXTAUTH_SECRET` | Secret used to sign NextAuth session tokens | Generate with `openssl rand -base64 32`. Must be a strong random string. | +| `GOOGLE_CLIENT_ID` | Google OAuth 2.0 client ID | [Google Cloud Console](https://console.cloud.google.com) → **APIs & Services → Credentials → Create Credentials → OAuth client ID** (Web application). | +| `GOOGLE_CLIENT_SECRET` | Google OAuth 2.0 client secret | Obtained alongside `GOOGLE_CLIENT_ID` in the same step above. | + +### GitHub App Variables (for PR reviews) + +| Variable | Description | How to obtain | +| :--- | :--- | :--- | +| `GITHUB_APP_ID` | Numeric ID of your GitHub App | [GitHub Developer Settings](https://github.com/settings/apps) → create or open your App → copy **App ID**. | +| `GITHUB_APP_PRIVATE_KEY` | RSA private key for the GitHub App | In your GitHub App settings → **Generate a private key** → paste contents with literal `\n` line breaks. | +| `GITHUB_APP_SLUG` | URL slug of your GitHub App | The part after `github.com/apps/` in the App's public URL. | +| `GITHUB_WEBHOOK_SECRET` | Secret to verify webhook payloads | Set any strong random string here and enter the same value in your GitHub App's webhook configuration. | + +### Optional Variables + +| Variable | Description | Default | +| :--- | :--- | :--- | +| `NEXT_PUBLIC_API_URL` | Base URL for client-side API calls | Current domain (e.g. `http://localhost:3000`) | +| `NEXT_PUBLIC_FIREBASE_API_KEY` | Firebase API key (App Hosting only) | Not required for local dev or Vercel | +| `GITHUB_APP_STATE_SECRET` | Additional signing secret for OAuth state | Falls back to `NEXTAUTH_SECRET` if unset | + +### Google OAuth Redirect URIs + +Add these **Authorized redirect URIs** in Google Cloud Console → **OAuth client**: + +| Environment | URI | +| :--- | :--- | +| Local dev | `http://localhost:3000/api/auth/callback/google` | +| Vercel | `https:///api/auth/callback/google` | + +--- + +## ðŸ› ï¸ Troubleshooting + +### `Error: PrismaClientInitializationError` / Cannot connect to database + +**Cause:** `DATABASE_URL` is missing, malformed, or the database is unreachable. + +**Fix:** +1. Verify `.env.local` contains `DATABASE_URL` and the value is correct. +2. Ensure your Neon database is not paused (Neon free-tier databases sleep after inactivity — open the Neon console to wake it). +3. Confirm the connection string includes `?sslmode=require`. +4. Run `npm run prisma:generate` followed by `npm run prisma:migrate` after any schema change. + +### `[next-auth][error][OAUTH_CALLBACK_ERROR]` during Google Sign-In + +**Cause:** `NEXTAUTH_URL`, `GOOGLE_CLIENT_ID`, or `GOOGLE_CLIENT_SECRET` is wrong, or the redirect URI is not registered in Google Cloud Console. + +**Fix:** +1. Double-check `NEXTAUTH_URL` matches the origin you are accessing (including protocol and port). +2. In Google Cloud Console → **OAuth client** → **Authorized redirect URIs**, ensure `/api/auth/callback/google` is listed. +3. On Vercel, set `NEXTAUTH_URL` to the exact deployment URL (no trailing slash). + +### `Error: NEXTAUTH_SECRET is not set` -- `DATABASE_URL` - PostgreSQL connection string -- `JWT_SECRET` - JWT secret key -- `GEMINI_API_KEY` - Google Gemini API key +**Cause:** `NEXTAUTH_SECRET` is missing from the environment. -OAuth (Google / NextAuth): +**Fix:** Generate a secret and add it to `.env.local`: -- `NEXTAUTH_URL` - Deployed base URL (e.g. `https://`) -- `NEXTAUTH_SECRET` - Session/JWT signing secret (generate with `openssl rand -base64 32`) -- `GOOGLE_CLIENT_ID` - Google OAuth client id -- `GOOGLE_CLIENT_SECRET` - Google OAuth client secret +```bash +openssl rand -base64 32 +``` + +On Vercel, add it under **Settings → Environment Variables**. + +### AI features return `500` / Gemini errors + +**Cause:** `GEMINI_API_KEY` is missing or invalid. + +**Fix:** +1. Confirm `GEMINI_API_KEY` is set in `.env.local`. +2. Verify the key is active in [Google AI Studio](https://aistudio.google.com/app/apikey). +3. Check that the Gemini API is enabled for your Google Cloud project. + +### Environment variables not picked up after editing `.env.local` + +**Fix:** Restart the development server — Next.js reads `.env.local` only at startup: + +```bash +# Stop the server (Ctrl+C), then: +npm run dev +``` + +On Vercel, trigger a redeploy (**Deployments → ⋯ → Redeploy**) after changing any environment variable in the dashboard. + +### `prisma:migrate` fails with `P3009` or migration drift + +**Cause:** Local database is out of sync with the migration history. + +**Fix (development only — do not run in production):** + +```bash +npx prisma migrate reset +npm run prisma:migrate +``` + +### Port 3000 already in use + +**Fix:** Kill the process using port 3000, or start on a different port: + +```bash +# Windows +netstat -ano | findstr :3000 +taskkill /PID /F + +# macOS / Linux +lsof -ti:3000 | xargs kill -9 + +# Or run on a different port +npm run dev -- -p 3001 +``` -Optional: +### Build fails on Vercel with `Type error` or missing module -- `NEXT_PUBLIC_API_URL` - API URL for client-side (defaults to current domain) +**Fix:** +1. Ensure all required environment variables are set in the Vercel dashboard — missing vars can cause build-time type errors. +2. Run `npm run build` locally first to catch errors before pushing. +3. Check that your Node.js version in Vercel matches the one used locally (see `engines` in `package.json`). ## 🤠Contributing From 3b78dbc25dc2a8c739837f093b1494daf678fb24 Mon Sep 17 00:00:00 2001 From: "NIVETHAALAPTOP\\meetn" Date: Thu, 21 May 2026 22:27:41 +0530 Subject: [PATCH 056/123] Remove unused cross-env dependency --- package-lock.json | 24 ------------------------ package.json | 1 - 2 files changed, 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index b0624906..d8df761c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,6 @@ "bcryptjs": "^3.0.3", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", - "cross-env": "^10.1.0", "d3": "^7.9.0", "dotenv": "^16.4.5", "google-auth-library": "^10.3.0", @@ -301,12 +300,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@epic-web/invariant": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", - "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", - "license": "MIT" - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.0", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", @@ -4802,23 +4795,6 @@ "node": ">= 0.6" } }, - "node_modules/cross-env": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", - "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", - "license": "MIT", - "dependencies": { - "@epic-web/invariant": "^1.0.0", - "cross-spawn": "^7.0.6" - }, - "bin": { - "cross-env": "dist/bin/cross-env.js", - "cross-env-shell": "dist/bin/cross-env-shell.js" - }, - "engines": { - "node": ">=20" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", diff --git a/package.json b/package.json index 064de38e..030b970c 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "bcryptjs": "^3.0.3", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", - "cross-env": "^10.1.0", "d3": "^7.9.0", "dotenv": "^16.4.5", "google-auth-library": "^10.3.0", From dbbc7bcd4b145a4b1cd9ec5faffc3df244084efd Mon Sep 17 00:00:00 2001 From: xyron24 Date: Thu, 21 May 2026 22:32:44 +0530 Subject: [PATCH 057/123] fix: resolve conflicting NEXTAUTH_URL guidance in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 587905e6..e348e409 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ const loadMore = async () => { 1. Push your code to GitHub. 2. Import the project in the [Vercel dashboard](https://vercel.com/new). 3. Under **Settings → Environment Variables**, add every variable listed in the [Environment Variables](#-environment-variables) section below. Vercel automatically makes them available at build time and runtime. - - For `NEXTAUTH_URL`, set the value to your Vercel deployment URL (e.g. `https://gitverse.vercel.app`). Leave it unset locally — NextAuth infers it automatically in development. + - For `NEXTAUTH_URL`, set the value to your Vercel deployment URL (e.g. `https://gitverse.vercel.app`). In local development, set it to `http://localhost:3000` in your `.env.local` to avoid missing-URL warnings. - Mark sensitive secrets (e.g. `JWT_SECRET`, `NEXTAUTH_SECRET`, `GOOGLE_CLIENT_SECRET`, `GEMINI_API_KEY`) as **Sensitive** in Vercel so they are never exposed in logs. 4. Click **Deploy**. From caee5a53a0e2fd181821692d10b7b6c5bcd1c648 Mon Sep 17 00:00:00 2001 From: Vijitha14 Date: Thu, 21 May 2026 22:40:29 +0530 Subject: [PATCH 058/123] Added keyboard shortcuts for search focus and clear --- package-lock.json | 10 +++++----- package.json | 4 ++-- src/pages/Dashboard.tsx | 37 +++++++++++++++++++++++++++++++++---- src/pages/LandingPage.tsx | 29 ++++++++++++++++++++++++++++- src/utils/cn.ts | 6 ++++++ 5 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 src/utils/cn.ts diff --git a/package-lock.json b/package-lock.json index 015d421e..6df83a3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "bcrypt": "^6.0.0", "bcryptjs": "^3.0.3", "class-variance-authority": "^0.7.0", - "clsx": "^2.0.0", + "clsx": "^2.1.1", "d3": "^7.9.0", "dotenv": "^16.4.5", "google-auth-library": "^10.3.0", @@ -43,7 +43,7 @@ "rehype-sanitize": "^6.0.0", "remark-gfm": "^4.0.1", "sonner": "^1.3.1", - "tailwind-merge": "^2.2.0", + "tailwind-merge": "^2.6.1", "ws": "^8.19.0" }, "devDependencies": { @@ -11593,9 +11593,9 @@ } }, "node_modules/tailwind-merge": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", - "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz", + "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==", "license": "MIT", "funding": { "type": "github", diff --git a/package.json b/package.json index dbf29097..035c27d7 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "bcrypt": "^6.0.0", "bcryptjs": "^3.0.3", "class-variance-authority": "^0.7.0", - "clsx": "^2.0.0", + "clsx": "^2.1.1", "d3": "^7.9.0", "dotenv": "^16.4.5", "google-auth-library": "^10.3.0", @@ -59,7 +59,7 @@ "rehype-sanitize": "^6.0.0", "remark-gfm": "^4.0.1", "sonner": "^1.3.1", - "tailwind-merge": "^2.2.0", + "tailwind-merge": "^2.6.1", "ws": "^8.19.0" }, "devDependencies": { diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 3f162a79..0ace28d9 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -2,7 +2,7 @@ export const dynamic = "force-dynamic"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } from "react"; import { useRouter } from "next/navigation"; import { GitBranch, @@ -51,10 +51,35 @@ export default function Dashboard() { const [repositories, setRepositories] = useState([]); const [loading, setLoading] = useState(true); const [analyzing, setAnalyzing] = useState(false); + const searchRef = useRef(null); useEffect(() => { - fetchRepositories(); - }, []); + fetchRepositories(); + + const handleKeyDown = (e: KeyboardEvent) => { + const active = document.activeElement; + + const isTyping = + active instanceof HTMLInputElement || + active instanceof HTMLTextAreaElement; + + if (e.key === "/" && !isTyping) { + e.preventDefault(); + searchRef.current?.focus(); + } + + if (e.key === "Escape") { + setRepoUrl(""); + searchRef.current?.blur(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; +}, []); const fetchRepositories = async () => { try { @@ -213,6 +238,7 @@ export default function Dashboard() { setRepoUrl(e.target.value)} className="flex-1 bg-background/50" @@ -226,6 +252,9 @@ export default function Dashboard() { {analyzing ? "Analyzing..." : "Analyze Repository"} +

+ Press "/" to focus search • Esc to clear +

@@ -440,4 +469,4 @@ export default function Dashboard() {
); -} +} \ No newline at end of file diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx index 1c36e24a..ecd5a116 100644 --- a/src/pages/LandingPage.tsx +++ b/src/pages/LandingPage.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import { GitBranch, @@ -29,6 +29,7 @@ export default function LandingPage() { const [repoUrl, setRepoUrl] = useState(""); const [isLoading, setIsLoading] = useState(false); const [scoreAnimate, setScoreAnimate] = useState(false); + const searchRef = useRef(null); const mentorMessages = useMemo( () => [ @@ -47,6 +48,31 @@ export default function LandingPage() { [], ); + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + const active = document.activeElement; + + const isTyping = + active instanceof HTMLInputElement || + active instanceof HTMLTextAreaElement; + + if (e.key === "/" && !isTyping) { + e.preventDefault(); + searchRef.current?.focus(); + } + + if (e.key === "Escape") { + setRepoUrl(""); + searchRef.current?.blur(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; +}, []); useEffect(() => { const media = window.matchMedia?.("(prefers-reduced-motion: reduce)"); if (media?.matches) { @@ -374,6 +400,7 @@ export default function LandingPage() { >
Date: Thu, 21 May 2026 22:42:25 +0530 Subject: [PATCH 059/123] docs: align Node.js version documentation --- GETTING_STARTED.md | 2 +- README.md | 4 ++++ START_HERE.md | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index 02eb2376..373b8fe9 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -4,7 +4,7 @@ This is the Next.js version of GitVerse, migrated from the Vite + React version ## Prerequisites -- Node.js 18+ installed +- Node.js 22.x installed (see [Supported Node Version](README.md#supported-node-version)) - PostgreSQL database (NeonDB recommended) - Google Gemini AI API key diff --git a/README.md b/README.md index 7f4575b6..e18aacb2 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,10 @@ Paste a repo → GitVerse builds a visual map + AI onboarding so contributors ca - Ask AI questions about files, folders, and architecture - Generate analysis jobs and track progress +## Supported Node Version + +This project officially supports **Node.js 22.x** (as specified in [package.json](package.json)). + ## Quickstart (local dev) ```bash diff --git a/START_HERE.md b/START_HERE.md index 7c851e85..6d095c9c 100644 --- a/START_HERE.md +++ b/START_HERE.md @@ -6,6 +6,10 @@ 📠/home/time_walker/projects/projects/gitverse/gitverse-nextjs/ ``` +## System Requirements + +- **Node.js 22.x** is officially supported. See [Supported Node Version](README.md#supported-node-version) in README for details. + ## Immediate Next Steps ### 1. Open in VS Code From e78c83377421af3bcda733a2d1fa55f966f090c3 Mon Sep 17 00:00:00 2001 From: swe296 Date: Thu, 21 May 2026 22:52:47 +0530 Subject: [PATCH 060/123] docs: add validation notes for AI analysis routes --- START_HERE.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/START_HERE.md b/START_HERE.md index 7c851e85..2249f8d7 100644 --- a/START_HERE.md +++ b/START_HERE.md @@ -240,7 +240,24 @@ git push origin main 4. Review Prisma docs: https://prisma.io/docs --- +## Validation Notes +The AI API routes validate request inputs before processing. + +Invalid requests return a `400 Bad Request` response with a helpful error message instead of exposing stack traces. + +Examples: +- Empty or missing `code` +- Empty or missing `language` +- Unsupported `analysisType` +- Oversized code snippets + +Example response: + +```json +{ + "error": "Code must be a non-empty string" +} ## 🚀 You're All Set! Your project has been successfully migrated to Next.js with: From 440e5c5640bcb15ef0f0c913cae7979a3f18f9c8 Mon Sep 17 00:00:00 2001 From: Vijitha14 Date: Thu, 21 May 2026 22:55:24 +0530 Subject: [PATCH 061/123] Fixed review comments from CodeRabbit --- src/pages/Dashboard.tsx | 4 ++-- src/pages/LandingPage.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 0ace28d9..7b86026f 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -68,7 +68,7 @@ export default function Dashboard() { searchRef.current?.focus(); } - if (e.key === "Escape") { + if (e.key === "Escape" && !isTyping) { setRepoUrl(""); searchRef.current?.blur(); } @@ -253,7 +253,7 @@ export default function Dashboard() { {analyzing ? "Analyzing..." : "Analyze Repository"}

- Press "/" to focus search • Esc to clear + Press "/" to focus search • Esc to clear

diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx index ecd5a116..9cd3dcae 100644 --- a/src/pages/LandingPage.tsx +++ b/src/pages/LandingPage.tsx @@ -61,7 +61,7 @@ export default function LandingPage() { searchRef.current?.focus(); } - if (e.key === "Escape") { + if (e.key === "Escape" && !isTyping) { setRepoUrl(""); searchRef.current?.blur(); } From 2d6831285c768f4c702b95b696e6685c48835479 Mon Sep 17 00:00:00 2001 From: Samarth Date: Thu, 21 May 2026 23:36:09 +0530 Subject: [PATCH 062/123] feat: implement empty-state UI system for repository analysis sections --- src/components/ai/CodeAnalysisPanel.tsx | 14 ++- .../repository/BranchVisualization.tsx | 12 ++- src/components/repository/CodeMetrics.tsx | 82 ++++++++++------- src/components/repository/CommitHistory.tsx | 16 +++- src/components/repository/Contributors.tsx | 14 ++- src/components/repository/FileStructure.tsx | 37 +++++--- .../repository/RepositoryOverview.tsx | 89 +++++++++++-------- .../visualizations/CodeDependencyGraph.tsx | 65 +++++++------- .../visualizations/CommitActivityHeatmap.tsx | 17 +++- .../LanguageDistributionChart.tsx | 83 +++++++++-------- src/pages/RepositoryAnalysis.tsx | 20 ++--- 11 files changed, 266 insertions(+), 183 deletions(-) diff --git a/src/components/ai/CodeAnalysisPanel.tsx b/src/components/ai/CodeAnalysisPanel.tsx index 08a4ee94..f693653c 100644 --- a/src/components/ai/CodeAnalysisPanel.tsx +++ b/src/components/ai/CodeAnalysisPanel.tsx @@ -7,7 +7,7 @@ import { Loader2, Sparkles, } from "lucide-react"; -import { Card } from "@/components/ui"; +import { Card, EmptyState } from "@/components/ui"; import { geminiService, CodeAnalysisRequest } from "@/services/gemini"; import { useToast } from "@/hooks/use-toast"; import { useAuth } from "@/contexts/AuthContext"; @@ -300,13 +300,11 @@ export function CodeAnalysisPanel() { {results.length === 0 ? ( -
- -

No analysis yet

-

- Enter your code and click analyze to get started -

-
+ ) : (
{results.map((result, index) => { diff --git a/src/components/repository/BranchVisualization.tsx b/src/components/repository/BranchVisualization.tsx index e0a6a790..e537cee9 100644 --- a/src/components/repository/BranchVisualization.tsx +++ b/src/components/repository/BranchVisualization.tsx @@ -1,5 +1,5 @@ import { GitBranch, Clock, User, CheckCircle, GitCommit } from "lucide-react"; -import { Card } from "@/components/ui"; +import { Card, EmptyState } from "@/components/ui"; import { useState } from "react"; interface Branch { @@ -110,6 +110,16 @@ export function BranchVisualization({ repository }: BranchVisualizationProps) { return "bg-green-500/20 text-green-500 border-green-500/30"; }; + if (branches.length === 0) { + return ( + + ); + } + return (
{/* Header */} diff --git a/src/components/repository/CodeMetrics.tsx b/src/components/repository/CodeMetrics.tsx index 5f46e02e..d5489abc 100644 --- a/src/components/repository/CodeMetrics.tsx +++ b/src/components/repository/CodeMetrics.tsx @@ -8,7 +8,7 @@ import { CheckCircle, Package, } from "lucide-react"; -import { Card } from "@/components/ui"; +import { Card, EmptyState } from "@/components/ui"; interface LanguageStat { name: string; @@ -276,6 +276,16 @@ export function CodeMetrics({ repository }: CodeMetricsProps) { } }; + if (!repository || (!repository.files?.length && !repository.languages?.length)) { + return ( + + ); + } + return (
{/* Header */} @@ -338,11 +348,11 @@ export function CodeMetrics({ repository }: CodeMetricsProps) {
) : ( - -

- No language data available -

-
+ )}
@@ -351,28 +361,36 @@ export function CodeMetrics({ repository }: CodeMetricsProps) {

File Type Distribution

-
- {fileTypeStats.map((fileType) => ( - -
-
- -
-
-

- {fileType.count} -

-

- {fileType.percentage.toFixed(2)}% -

+ {fileTypeStats.length > 0 ? ( +
+ {fileTypeStats.map((fileType) => ( + +
+
+ +
+
+

+ {fileType.count} +

+

+ {fileType.percentage.toFixed(2)}% +

+
-
-

- {fileType.type} -

- - ))} -
+

+ {fileType.type} +

+ + ))} +
+ ) : ( + + )}
{/* Quality metrics */} @@ -588,11 +606,11 @@ export function CodeMetrics({ repository }: CodeMetricsProps) {
) : ( -
-

- No test files detected in repository -

-
+ )} ); diff --git a/src/components/repository/CommitHistory.tsx b/src/components/repository/CommitHistory.tsx index 3376acd2..cf991776 100644 --- a/src/components/repository/CommitHistory.tsx +++ b/src/components/repository/CommitHistory.tsx @@ -1,6 +1,6 @@ -import { FileText, Plus, Minus, GitMerge, Tag } from "lucide-react"; - +import { FileText, Plus, Minus, GitMerge, Tag, GitCommit } from "lucide-react"; import { useState, useMemo } from "react"; +import { EmptyState } from "@/components/ui"; interface FileChange { path: string; @@ -320,7 +320,14 @@ export const CommitHistory = ({ repository }: CommitHistoryProps) => {
{/* Graph Timeline */} -
+ {commits.length === 0 ? ( + + ) : ( +
{graphNodes.map((node, index) => { const { commit, column, color, routes } = node; @@ -601,7 +608,8 @@ export const CommitHistory = ({ repository }: CommitHistoryProps) => { ); })}
-
+
+ )}
); }; diff --git a/src/components/repository/Contributors.tsx b/src/components/repository/Contributors.tsx index 42a3048a..f52c2157 100644 --- a/src/components/repository/Contributors.tsx +++ b/src/components/repository/Contributors.tsx @@ -11,7 +11,7 @@ import { Clock, Zap, } from "lucide-react"; -import { Card } from "@/components/ui"; +import { Card, EmptyState } from "@/components/ui"; import { useState, useRef, useEffect } from "react"; interface Contributor { @@ -125,7 +125,15 @@ export function Contributors({ repository }: ContributorsProps) {
{/* Stats overview */} -
+ {contributors.length === 0 ? ( + + ) : ( + <> +
@@ -310,6 +318,8 @@ export function Contributors({ repository }: ContributorsProps) { ); })}
+ + )} {/* Selected contributor modal */} {selectedContributor && ( diff --git a/src/components/repository/FileStructure.tsx b/src/components/repository/FileStructure.tsx index 9ca1fe37..a8212a47 100644 --- a/src/components/repository/FileStructure.tsx +++ b/src/components/repository/FileStructure.tsx @@ -17,6 +17,7 @@ import { CardTitle, CardDescription, CardContent, + EmptyState, } from "@/components/ui"; interface FileNode { @@ -209,20 +210,28 @@ export const FileStructure = ({ repository }: FileStructureProps) => { return (
- - - File Structure - - Explore the repository's file system - - *Click on a file for more info* - - -
- -
-
-
+ {!repository?.files || repository.files.length === 0 ? ( + + ) : ( + + + File Structure + + Explore the repository's file system + + *Click on a file for more info* + + +
+ +
+
+
+ )} {/* File Analytics Modal */} {selectedFile && ( diff --git a/src/components/repository/RepositoryOverview.tsx b/src/components/repository/RepositoryOverview.tsx index 1588388c..2219c15c 100644 --- a/src/components/repository/RepositoryOverview.tsx +++ b/src/components/repository/RepositoryOverview.tsx @@ -20,6 +20,7 @@ import { CardDescription, CardContent, Skeleton, + EmptyState, } from "@/components/ui"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; @@ -363,24 +364,32 @@ export const RepositoryOverview = ({ - {languages.map((lang: any) => ( -
-
- - {lang.name} - - - {lang.percentage}% - -
-
-
+ {languages.length > 0 ? ( + languages.map((lang: any) => ( +
+
+ + {lang.name} + + + {lang.percentage}% + +
+
+
+
-
- ))} + )) + ) : ( + + )} @@ -395,26 +404,34 @@ export const RepositoryOverview = ({ -
- {recentActivity.map((activity: any, index: number) => ( -
-
- + {recentActivity.length > 0 ? ( +
+ {recentActivity.map((activity: any, index: number) => ( +
+
+ +
+
+

+ {activity.user}{" "} + + {activity.message} + +

+

+ {activity.time} +

+
-
-

- {activity.user}{" "} - - {activity.message} - -

-

- {activity.time} -

-
-
- ))} -
+ ))} +
+ ) : ( + + )}
diff --git a/src/components/visualizations/CodeDependencyGraph.tsx b/src/components/visualizations/CodeDependencyGraph.tsx index d3eb3f24..d712cd03 100644 --- a/src/components/visualizations/CodeDependencyGraph.tsx +++ b/src/components/visualizations/CodeDependencyGraph.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef } from "react"; import * as d3 from "d3"; -import { Card } from "@/components/ui"; +import { Card, EmptyState } from "@/components/ui"; +import { Network } from "lucide-react"; interface Node { id: string; @@ -111,23 +112,13 @@ export function CodeDependencyGraph({ repository }: CodeDependencyGraphProps) { const svgRef = useRef(null); const tooltipRef = useRef(null); + const graphData = generateDependencyGraph(repository); + useEffect(() => { if (!svgRef.current) return; - const graphData = generateDependencyGraph(repository); - // If no data, show empty state if (graphData.nodes.length === 0) { - const svg = d3.select(svgRef.current); - svg.selectAll("*").remove(); - svg - .append("text") - .attr("x", "50%") - .attr("y", "50%") - .attr("text-anchor", "middle") - .attr("dominant-baseline", "middle") - .attr("fill", "rgba(255,255,255,0.4)") - .text("No files found in repository"); return; } @@ -356,25 +347,35 @@ export function CodeDependencyGraph({ repository }: CodeDependencyGraphProps) {
-
-

- Code Dependencies -

-
- -
-
-

- 💡 Drag nodes to reposition • Scroll to zoom • Hover for details -

+ {graphData.nodes.length === 0 ? ( + + ) : ( + <> +
+

+ Code Dependencies +

+
+ +
+
+

+ 💡 Drag nodes to reposition • Scroll to zoom • Hover for details +

+ + )}
-
- + ) : ( + <> +
+
)} + + )}
-
- -
-
- {languageData.length > 0 ? ( - languageData.map((lang) => ( -
-
-
- {lang.name} -
-
- - {lang.lines.toLocaleString()} lines - - - {lang.percentage.toFixed(2)}% - + ) : ( + <> +
+ +
+
+ {languageData.map((lang) => ( +
+
+
+ {lang.name} +
+
+ + {lang.lines.toLocaleString()} lines + + + {lang.percentage.toFixed(2)}% + +
-
- )) - ) : ( -

- No language data available -

- )} -
+ ))} +
+ + )}
Loading repository...

) : !job ? ( -
- -
-

No analysis jobs found

-

- Run your first analysis to get started -

-
- -
+ router.push("/dashboard")} + /> ) : ( <> {/* Header with back button */} From 03451aba6aaeeadc8d87a5919c6af8492c0732ea Mon Sep 17 00:00:00 2001 From: nisshchayarathi Date: Thu, 21 May 2026 18:29:20 +0000 Subject: [PATCH 063/123] Add cn helper alias --- src/utils/cn.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/utils/cn.ts diff --git a/src/utils/cn.ts b/src/utils/cn.ts new file mode 100644 index 00000000..0b14f14c --- /dev/null +++ b/src/utils/cn.ts @@ -0,0 +1 @@ +export { cn } from '@/lib/utils' \ No newline at end of file From 127552120b6a980a4395c2d334dde13a3d84cc4c Mon Sep 17 00:00:00 2001 From: Sudeepa980 Date: Fri, 22 May 2026 00:06:38 +0530 Subject: [PATCH 064/123] docs: add Vercel deployment checklist --- README.md | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7f4575b6..beab2311 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,91 @@ OAuth (Google / NextAuth): Optional: - `NEXT_PUBLIC_API_URL` - API URL for client-side (defaults to current domain) + +## ✅ Vercel Deployment Checklist + +Before deploying GitVerse to Vercel, make sure all required environment variables are added correctly in the Vercel dashboard. + +You can add them from: + +Settings → Environment Variables + +### Required Environment Variables + +| Variable | Purpose | +|---|---| +| `DATABASE_URL` | PostgreSQL database connection string | +| `JWT_SECRET` | Secret used for JWT signing | +| `GEMINI_API_KEY` | Google Gemini API key | +| `NEXTAUTH_URL` | Your deployed application URL | +| `NEXTAUTH_SECRET` | Secret used by NextAuth | +| `GOOGLE_CLIENT_ID` | Google OAuth client ID | +| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret | + +## âš ï¸ Common Deployment Issues + +### Missing Environment Variables + +If the app fails during build or authentication does not work properly: + +- Check whether all required variables are added +- Make sure variable names are spelled correctly +- Avoid adding extra spaces or quotes + +### Environment Variables Not Updating + +Sometimes Vercel continues using older values after changes. + +Try redeploying the project manually after updating environment variables. + +### `NEXT_PUBLIC_` Variables + +Only variables starting with `NEXT_PUBLIC_` are available on the client side. + +Example: + +```env +NEXT_PUBLIC_API_URL=https://your-domain.vercel.app +``` + +### OAuth Redirect Errors + +Make sure the Google OAuth redirect URL matches: + +```text +https://your-domain.vercel.app/api/auth/callback/google +``` + +Also verify that `NEXTAUTH_URL` matches your deployed domain. + +### Database Connection Issues + +If Prisma cannot connect to the database: + +- Verify the `DATABASE_URL` +- Ensure the database accepts external connections +- Run migrations before deployment if needed + +### Build Failures on Vercel + +Before pushing changes, test the build locally: + +```bash +npm run prisma:generate +npm run build +``` + +## ✅ Recommended Deployment Steps + +1. Push the project to GitHub +2. Import the repository into Vercel +3. Add all required environment variables +4. Redeploy after updating variables +5. Test authentication and API routes after deployment + + + + ## 🤠Contributing @@ -290,6 +375,6 @@ This project is licensed under the MIT License. - NeonDB for serverless PostgreSQL - All contributors and users of GitVerse ---- + Made with â¤ï¸ by the GitVerse Team From 540f7acb921cb88aa40edc31d225817ceec3be83 Mon Sep 17 00:00:00 2001 From: Nisshchaya Rathi Date: Fri, 22 May 2026 00:54:34 +0530 Subject: [PATCH 065/123] Revert "fix(db): add index on GitHubRepo for webhook repo lookup" --- app/api/ai/analyze-code/route.ts | 21 +----- app/api/ai/analyze-repository/route.ts | 24 ++----- app/api/ai/chat/route.ts | 44 +------------ app/api/ai/explain-file/route.ts | 19 +----- app/api/ai/review-pr/route.ts | 5 ++ app/api/ai/suggest-commit/route.ts | 13 ++-- prisma/schema.prisma | 3 - src/components/ui/Skeleton.tsx | 12 ---- src/components/ui/index.ts | 1 - src/pages/Dashboard.tsx | 91 ++++++++------------------ src/pages/RepositoryAnalysis.tsx | 31 +-------- src/pages/SearchPage.tsx | 1 - tsconfig.json | 2 +- 13 files changed, 55 insertions(+), 212 deletions(-) delete mode 100644 src/components/ui/Skeleton.tsx diff --git a/app/api/ai/analyze-code/route.ts b/app/api/ai/analyze-code/route.ts index 97a0ab51..dc2d9e4e 100644 --- a/app/api/ai/analyze-code/route.ts +++ b/app/api/ai/analyze-code/route.ts @@ -8,24 +8,9 @@ export async function POST(request: NextRequest) { const body = await request.json(); const { code, language, analysisType, context } = body; - if (typeof code !== "string" || !code.trim()) { + if (!code || !language || !analysisType) { return NextResponse.json( - { error: "Code must be a non-empty string" }, - { status: 400 } - ); - } - - if (typeof language !== "string" || !language.trim()) { - return NextResponse.json( - { error: "Language must be a non-empty string" }, - { status: 400 } - ); - } - - const validAnalysisTypes = ["explain", "improve", "bugs", "document", "refactor"]; - if (typeof analysisType !== "string" || !validAnalysisTypes.includes(analysisType)) { - return NextResponse.json( - { error: `Analysis type must be one of: ${validAnalysisTypes.join(", ")}` }, + { error: "Code, language, and analysis type are required" }, { status: 400 } ); } @@ -40,7 +25,7 @@ export async function POST(request: NextRequest) { const analysis = await getGeminiService().analyzeCode({ code, language, - analysisType: analysisType as "explain" | "improve" | "bugs" | "document" | "refactor", + analysisType, context, }); diff --git a/app/api/ai/analyze-repository/route.ts b/app/api/ai/analyze-repository/route.ts index 661488b0..8e64eece 100644 --- a/app/api/ai/analyze-repository/route.ts +++ b/app/api/ai/analyze-repository/route.ts @@ -9,31 +9,15 @@ export async function POST(request: NextRequest) { const body = await request.json(); const { repositoryId, type } = body; - if (repositoryId == null || type == null) { + if (!repositoryId || !type) { return NextResponse.json( { error: "Repository ID and analysis type are required" }, { status: 400 } ); } - const parsedRepoId = Number(repositoryId); - if (!Number.isFinite(parsedRepoId)) { - return NextResponse.json( - { error: "Repository ID must be a valid number" }, - { status: 400 } - ); - } - - const validTypes = ["overview", "code-quality", "security", "architecture", "suggestions"]; - if (typeof type !== "string" || !validTypes.includes(type)) { - return NextResponse.json( - { error: `Analysis type must be one of: ${validTypes.join(", ")}` }, - { status: 400 } - ); - } - const repository = await repositoryService.getRepository( - parsedRepoId, + repositoryId, user.userId ); @@ -61,8 +45,8 @@ export async function POST(request: NextRequest) { }; const analysis = await getGeminiService().analyzeRepository({ - repositoryId: parsedRepoId, - type: type as "overview" | "code-quality" | "security" | "architecture" | "suggestions", + repositoryId, + type, context, }); diff --git a/app/api/ai/chat/route.ts b/app/api/ai/chat/route.ts index 1a3b5fa2..6d258259 100644 --- a/app/api/ai/chat/route.ts +++ b/app/api/ai/chat/route.ts @@ -15,53 +15,15 @@ export async function POST(request: NextRequest) { return NextResponse.json({ response }); } - if (prompt !== undefined && typeof prompt !== "string") { - return NextResponse.json( - { error: "Prompt must be a string" }, - { status: 400 } - ); - } - - if (repositoryId == null || question == null) { + if (!repositoryId || !question) { return NextResponse.json( { error: "Repository ID and question are required" }, { status: 400 } ); } - const parsedRepoId = Number(repositoryId); - if (!Number.isFinite(parsedRepoId)) { - return NextResponse.json( - { error: "Repository ID must be a valid number" }, - { status: 400 } - ); - } - - if (typeof question !== "string" || !question.trim()) { - return NextResponse.json( - { error: "Question must be a non-empty string" }, - { status: 400 } - ); - } - - if ( - conversationHistory !== undefined && - (!Array.isArray(conversationHistory) || - conversationHistory.some( - (m: any) => - typeof m !== "object" || - !["user", "assistant"].includes(m.role) || - typeof m.content !== "string" - )) - ) { - return NextResponse.json( - { error: "conversationHistory must be an array of {role, content} objects" }, - { status: 400 } - ); - } - const repository = await repositoryService.getRepository( - parsedRepoId, + repositoryId, user.userId ); @@ -86,7 +48,7 @@ export async function POST(request: NextRequest) { }; const response = await getGeminiService().chatAboutRepository({ - repositoryId: parsedRepoId, + repositoryId, question, conversationHistory, context, diff --git a/app/api/ai/explain-file/route.ts b/app/api/ai/explain-file/route.ts index 661f3aab..4819b5b0 100644 --- a/app/api/ai/explain-file/route.ts +++ b/app/api/ai/explain-file/route.ts @@ -30,30 +30,15 @@ export async function POST(request: NextRequest) { const body = await request.json(); const { repositoryId, filePath } = body; - if (repositoryId == null || filePath == null) { + if (!repositoryId || !filePath) { return NextResponse.json( { error: "Repository ID and file path are required" }, { status: 400 } ); } - const parsedRepoId = Number(repositoryId); - if (!Number.isFinite(parsedRepoId)) { - return NextResponse.json( - { error: "Repository ID must be a valid number" }, - { status: 400 } - ); - } - - if (typeof filePath !== "string" || !filePath.trim()) { - return NextResponse.json( - { error: "File path must be a non-empty string" }, - { status: 400 } - ); - } - const repository = (await repositoryService.getRepository( - parsedRepoId, + repositoryId, user.userId )) as Repository; diff --git a/app/api/ai/review-pr/route.ts b/app/api/ai/review-pr/route.ts index be0c562c..0a4c0a90 100644 --- a/app/api/ai/review-pr/route.ts +++ b/app/api/ai/review-pr/route.ts @@ -51,4 +51,9 @@ export async function POST(request: NextRequest) { { status: 500 }, ); } + + return NextResponse.json( + { error: "Failed to review PR", details: "Unexpected fallthrough" }, + { status: 500 }, + ); } diff --git a/app/api/ai/suggest-commit/route.ts b/app/api/ai/suggest-commit/route.ts index 8de1db9c..aff6393e 100644 --- a/app/api/ai/suggest-commit/route.ts +++ b/app/api/ai/suggest-commit/route.ts @@ -8,13 +8,12 @@ export async function POST(request: NextRequest) { const body = await request.json(); const { added, modified, deleted, diff } = body; - const hasValidInput = - (Array.isArray(added) && added.length > 0) || - (Array.isArray(modified) && modified.length > 0) || - (Array.isArray(deleted) && deleted.length > 0) || - (typeof diff === "string" && diff.trim().length > 0); - - if (!hasValidInput) { + if ( + (!added || added.length === 0) && + (!modified || modified.length === 0) && + (!deleted || deleted.length === 0) && + !diff + ) { return NextResponse.json( { error: "At least one of added, modified, deleted, or diff is required" }, { status: 400 } diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 56ef3142..f05891b4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -240,7 +240,6 @@ model Branch { updatedAt DateTime @updatedAt @map("updated_at") @@unique([repositoryId, name]) - @@index([repositoryId]) @@map("branches") } @@ -324,7 +323,6 @@ model Contributor { updatedAt DateTime @updatedAt @map("updated_at") @@unique([repositoryId, email]) - @@index([repositoryId]) @@map("contributors") } @@ -340,6 +338,5 @@ model Language { createdAt DateTime @default(now()) @map("created_at") @@unique([repositoryId, name]) - @@index([repositoryId]) @@map("languages") } diff --git a/src/components/ui/Skeleton.tsx b/src/components/ui/Skeleton.tsx deleted file mode 100644 index a9e1c115..00000000 --- a/src/components/ui/Skeleton.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { cn } from "@/lib/utils"; - -interface SkeletonProps extends React.HTMLAttributes {} - -export function Skeleton({ className, ...props }: SkeletonProps) { - return ( -
- ); -} diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index 9e0589da..f0d1b29d 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -23,4 +23,3 @@ export { DropdownMenuSubTrigger, DropdownMenuRadioGroup, } from './dropdown-menu' -export { Skeleton } from './Skeleton' diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index e15658c3..3f162a79 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -24,7 +24,6 @@ import { Button, Input, EmptyState, - Skeleton, } from "@/components/ui"; import { useAuth } from "@/contexts/AuthContext"; import { buildApiUrl } from "@/services/apiConfig"; @@ -233,48 +232,33 @@ export default function Dashboard() { {/* Stats Grid */}
- {loading - ? Array.from({ length: 4 }).map((_, i) => ( - - -
-
- - - -
- -
-
-
- )) - : stats.map((stat, index) => ( - - -
-
-

- {stat.label} -

-

- {stat.value} -

-

- - {stat.change} -

-
-
- -
-
-
-
- ))} + {stats.map((stat, index) => ( + + +
+
+

+ {stat.label} +

+

+ {stat.value} +

+

+ + {stat.change} +

+
+
+ +
+
+
+
+ ))}
@@ -303,25 +287,8 @@ export default function Dashboard() { {loading ? ( -
- {Array.from({ length: 3 }).map((_, i) => ( -
-
- -
- - -
-
-
- - -
-
- ))} +
+ Loading repositories...
) : recentRepositories.length === 0 ? (
{loading ? ( -
- {/* Header Skeleton */} -
- -
- - -
- -
- - {/* Tabs Skeleton */} -
-
- {Array.from({ length: 6 }).map((_, i) => ( - - ))} -
-
- - {/* Content Skeleton */} -
- -
- - -
-
+
+

Loading repository...

) : !job ? ( Date: Fri, 22 May 2026 00:55:55 +0530 Subject: [PATCH 066/123] Revert "fix(db): add indexes on Account and Session for user-related queries" From 52a5e0a2c82b944786f3e522bbda19ebc6ada1fd Mon Sep 17 00:00:00 2001 From: Nisshchaya Rathi Date: Fri, 22 May 2026 00:58:55 +0530 Subject: [PATCH 067/123] Revert "fix(db): add indexes on Branch, Contributor, and Language for repository queries" From bba9e67075b4d31f96140ed5f167b7b4a46de803 Mon Sep 17 00:00:00 2001 From: Nisshchaya Rathi Date: Fri, 22 May 2026 01:01:15 +0530 Subject: [PATCH 068/123] Revert "fix(security): enforce ownership and no-store caching on webhook endpoint" --- app/api/analysis-jobs/[id]/route.ts | 8 -- app/api/analysis/[jobId]/route.ts | 8 -- app/api/webhooks/github/route.ts | 100 +++++++++++------- prisma/schema.prisma | 1 - .../ui/RepositoryAnalysisSkeleton.tsx | 39 ------- src/pages/RepositoryAnalysis.tsx | 1 - 6 files changed, 64 insertions(+), 93 deletions(-) delete mode 100644 src/components/ui/RepositoryAnalysisSkeleton.tsx diff --git a/app/api/analysis-jobs/[id]/route.ts b/app/api/analysis-jobs/[id]/route.ts index e1e4cf9e..b9bf0263 100644 --- a/app/api/analysis-jobs/[id]/route.ts +++ b/app/api/analysis-jobs/[id]/route.ts @@ -11,14 +11,6 @@ export async function GET( const user = await requireAuth(request); const jobId = params.id; - const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - if (!uuidPattern.test(jobId)) { - return NextResponse.json( - { error: "Invalid job ID format. Expected a UUID" }, - { status: 400 } - ); - } - const job = await analysisJobService.getJob({ jobId, userId: user.userId, diff --git a/app/api/analysis/[jobId]/route.ts b/app/api/analysis/[jobId]/route.ts index 75fdcf8b..55451a28 100644 --- a/app/api/analysis/[jobId]/route.ts +++ b/app/api/analysis/[jobId]/route.ts @@ -14,14 +14,6 @@ export async function GET( return NextResponse.json({ error: "Missing jobId" }, { status: 400 }); } - const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - if (!uuidPattern.test(jobId)) { - return NextResponse.json( - { error: "Invalid job ID format. Expected a UUID" }, - { status: 400 } - ); - } - const job = await analysisJobService.getJob({ jobId, userId: user.userId }); if (!job) { diff --git a/app/api/webhooks/github/route.ts b/app/api/webhooks/github/route.ts index 5cbfb86a..eba6ba72 100644 --- a/app/api/webhooks/github/route.ts +++ b/app/api/webhooks/github/route.ts @@ -36,13 +36,6 @@ function shouldHandlePullRequestAction(action: string | undefined): boolean { ); } -function noStoreResponse(data: unknown, status = 200) { - return NextResponse.json(data, { - status, - headers: { "Cache-Control": "no-store" }, - }); -} - export async function POST(request: NextRequest) { const rawBody = await request.text(); @@ -57,33 +50,45 @@ export async function POST(request: NextRequest) { webhookSecret: secret, }) ) { - return noStoreResponse({ error: "Invalid signature" }, 401); + return NextResponse.json({ error: "Invalid signature" }, { status: 401 }); } if (event !== "pull_request") { - return noStoreResponse({ ok: true, ignored: true, event }); + return NextResponse.json( + { ok: true, ignored: true, event }, + { status: 200 }, + ); } let payload: PullRequestWebhookPayload; try { payload = JSON.parse(rawBody); } catch { - return noStoreResponse({ error: "Invalid JSON" }, 400); + return NextResponse.json({ error: "Invalid JSON" }, { status: 400 }); } const action = payload.action; if (!shouldHandlePullRequestAction(action)) { - return noStoreResponse({ ok: true, ignored: true, action }); + return NextResponse.json( + { ok: true, ignored: true, action }, + { status: 200 }, + ); } // Ignore draft PRs until they become ready_for_review if (payload.pull_request?.draft && action !== "ready_for_review") { - return noStoreResponse({ ok: true, ignored: true, reason: "draft" }); + return NextResponse.json( + { ok: true, ignored: true, reason: "draft" }, + { status: 200 }, + ); } // Avoid replying to bots (including ourselves) if (payload.sender?.type === "Bot") { - return noStoreResponse({ ok: true, ignored: true, reason: "bot" }); + return NextResponse.json( + { ok: true, ignored: true, reason: "bot" }, + { status: 200 }, + ); } const owner = payload.repository?.owner?.login; @@ -92,14 +97,20 @@ export async function POST(request: NextRequest) { const installationId = payload.installation?.id; if (!owner || !repo || !number || !installationId) { - return noStoreResponse({ error: "Missing required fields" }, 400); + return NextResponse.json( + { + error: "Missing required fields", + details: { owner, repo, number, installationId }, + }, + { status: 400 }, + ); } try { const repoFullName = `${owner}/${repo}`; // Gate by DB selection: only auto-review repos that users explicitly enabled. - // Verify the installation belongs to a registered user to enforce ownership. + // Hackathon scope: if *any* user enabled this repo, we process and attach the PR to that GitHubRepo row. const enabledRepo = await prisma.gitHubRepo.findFirst({ where: { repoFullName, @@ -113,7 +124,10 @@ export async function POST(request: NextRequest) { }); if (!enabledRepo) { - return noStoreResponse({ ok: true, ignored: true, reason: "repo_not_enabled" }); + return NextResponse.json( + { ok: true, ignored: true, reason: "repo_not_enabled", repoFullName }, + { status: 200 }, + ); } // Backfill installationId for future lookups. @@ -134,9 +148,12 @@ export async function POST(request: NextRequest) { const pr = await github.getPullRequest(owner, repo, number); const headSha = pr?.head?.sha; if (!headSha) { - return noStoreResponse( - { error: "Missing head SHA from GitHub PR response" }, - 500 + return NextResponse.json( + { + error: "Missing head SHA from GitHub PR response", + details: { owner, repo, number }, + }, + { status: 500 }, ); } @@ -185,11 +202,17 @@ export async function POST(request: NextRequest) { }); } catch (e: any) { if (e?.code === "P2002") { - return noStoreResponse({ - ok: true, - ignored: true, - reason: "already_reviewed", - }); + return NextResponse.json( + { + ok: true, + ignored: true, + reason: "already_reviewed", + repoFullName, + prNumber: number, + headSha, + }, + { status: 200 }, + ); } throw e; } @@ -254,21 +277,26 @@ export async function POST(request: NextRequest) { }, }); - return noStoreResponse({ - ok: true, - posted: postedUrl, - postError, - stored: { - pullRequestId: prRecord.id, - prReviewId: reviewRow.id, - headSha, + return NextResponse.json( + { + ok: true, + posted: postedUrl, + postError, + stored: { + pullRequestId: prRecord.id, + prReviewId: reviewRow.id, + headSha, + }, }, - }); + { status: 200 }, + ); } catch (error: any) { console.error("GitHub webhook PR review error:", error); - return noStoreResponse( - { error: "Failed to process PR webhook" }, - 500 + return NextResponse.json( + { + error: "Failed to process PR webhook", + }, + { status: 500 }, ); } } diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 13c9d3c0..f05891b4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -146,7 +146,6 @@ model Session { user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) - @@index([expires]) @@map("sessions") } diff --git a/src/components/ui/RepositoryAnalysisSkeleton.tsx b/src/components/ui/RepositoryAnalysisSkeleton.tsx deleted file mode 100644 index fa9db0d4..00000000 --- a/src/components/ui/RepositoryAnalysisSkeleton.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Skeleton } from "./Skeleton"; - -export const RepositoryAnalysisSkeleton: React.FC = () => { - return ( -
- {/* Header skeleton */} -
- -
- - -
- -
-
- -
- - {/* Tab navigation skeleton */} -
-
- {Array.from({ length: 6 }).map((_, i) => ( - - ))} -
-
- - {/* Content skeleton */} -
- - - - - - -
-
- ); -}; diff --git a/src/pages/RepositoryAnalysis.tsx b/src/pages/RepositoryAnalysis.tsx index 3b9dd3cb..ca7b3179 100644 --- a/src/pages/RepositoryAnalysis.tsx +++ b/src/pages/RepositoryAnalysis.tsx @@ -28,7 +28,6 @@ import axios from "axios"; import { useToast } from "@/hooks/use-toast"; import { EmptyState } from "@/components/ui"; import { buildApiUrl } from "@/services/apiConfig"; -import { RepositoryAnalysisSkeleton } from "@/components/ui/RepositoryAnalysisSkeleton"; type TabType = | "overview" From 449e6e7a1d0610edea803598ca8a4f067b13251b Mon Sep 17 00:00:00 2001 From: Sudeepa980 Date: Fri, 22 May 2026 03:40:17 +0530 Subject: [PATCH 069/123] Improve repository page usability and error handling --- app/api/ai/analyze-repository/route.ts | 79 +- package-lock.json | 2255 +++++++++++++----------- package.json | 37 +- src/components/ui/Skeleton.tsx | 6 +- 4 files changed, 1360 insertions(+), 1017 deletions(-) diff --git a/app/api/ai/analyze-repository/route.ts b/app/api/ai/analyze-repository/route.ts index 8e64eece..88cc06d9 100644 --- a/app/api/ai/analyze-repository/route.ts +++ b/app/api/ai/analyze-repository/route.ts @@ -4,11 +4,20 @@ import { getGeminiService } from "@/lib/services/geminiService"; import { repositoryService } from "@/lib/services/repositoryService"; export async function POST(request: NextRequest) { + const startTime = Date.now(); + try { const user = await requireAuth(request); + const body = await request.json(); const { repositoryId, type } = body; + console.log("[RunAnalysis] Started", { + userId: user.userId, + repositoryId, + type, + }); + if (!repositoryId || !type) { return NextResponse.json( { error: "Repository ID and analysis type are required" }, @@ -28,31 +37,70 @@ export async function POST(request: NextRequest) { ); } + // Build lightweight context (efficiency improvement) const context = { - languages: repository.languages.map((l: any) => ({ + languages: repository.languages?.map((l: any) => ({ name: l.name, percentage: l.percentage, - })), - contributors: repository.contributors.map((c: any) => ({ + })) || [], + + contributors: repository.contributors?.map((c: any) => ({ name: c.name, commits: c.commits, - })), - commits: repository.commits.slice(0, 10).map((c: any) => ({ + })) || [], + + commits: repository.commits?.slice(0, 10).map((c: any) => ({ message: c.message, author: c.authorName, - date: c.committedAt.toISOString(), - })), + date: c.committedAt?.toISOString(), + })) || [], }; - const analysis = await getGeminiService().analyzeRepository({ + console.log("[RunAnalysis] Context prepared", { + languages: context.languages.length, + contributors: context.contributors.length, + commits: context.commits.length, + }); + + // Timeout safety for Vercel (important improvement) + const analysisPromise = getGeminiService().analyzeRepository({ repositoryId, type, context, }); - return NextResponse.json({ analysis, type }); + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error("Analysis timeout exceeded")), 25000) + ); + + const analysis = await Promise.race([ + analysisPromise, + timeoutPromise, + ]); + + const duration = Date.now() - startTime; + + console.log("[RunAnalysis] Completed", { + repositoryId, + duration: `${duration}ms`, + }); + + return NextResponse.json({ + analysis, + type, + meta: { + duration, + success: true, + }, + }); + } catch (error: any) { - console.error("Repository analysis error:", error); + const duration = Date.now() - startTime; + + console.error("[RunAnalysis] Failed", { + error: error?.message, + duration: `${duration}ms`, + }); if (isHttpError(error)) { return NextResponse.json( @@ -60,9 +108,16 @@ export async function POST(request: NextRequest) { { status: error.status } ); } + return NextResponse.json( - { error: "Failed to analyze repository" }, + { + error: "Failed to analyze repository", + meta: { + duration, + success: false, + }, + }, { status: 500 } ); } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 015d421e..cde0693b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,6 +55,7 @@ "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^6.14.0", "autoprefixer": "^10.4.16", + "cross-env": "^10.1.0", "eslint": "^8.55.0", "eslint-config-next": "^14.1.0", "postcss": "^8.4.32", @@ -81,22 +82,103 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@auth/core": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.34.3.tgz", + "integrity": "sha512-jMjY/S0doZnWYNV90x0jmU3B+UcrsfGYnukxYrRbj0CVvGI/MX3JbHsxSrx2d4mbnXaUsqJmAcDfoQWA6r0lOw==", + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "@panva/hkdf": "^1.1.1", + "@types/cookie": "0.6.0", + "cookie": "0.6.0", + "jose": "^5.1.3", + "oauth4webapi": "^2.10.4", + "preact": "10.11.3", + "preact-render-to-string": "5.2.3" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "nodemailer": "^7" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/@auth/core/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@auth/core/node_modules/jose": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@auth/core/node_modules/preact": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/@auth/core/node_modules/preact-render-to-string": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", + "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/@auth/prisma-adapter": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@auth/prisma-adapter/-/prisma-adapter-2.11.1.tgz", - "integrity": "sha512-Ke7DXP0Fy0Mlmjz/ZJLXwQash2UkA4621xCM0rMtEczr1kppLc/njCbUkHkIQ/PnmILjqSPEKeTjDPsYruvkug==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@auth/prisma-adapter/-/prisma-adapter-2.11.2.tgz", + "integrity": "sha512-GyNEUNtrPgDPs0M4xX6F5i7jTsCKwU6BXV9zutctcoo6K1Ud+juckrmQS11uyNgeWsw6sliextHbU/e+8lsizQ==", "license": "ISC", "dependencies": { - "@auth/core": "0.41.1" + "@auth/core": "0.41.2" }, "peerDependencies": { "@prisma/client": ">=2.26.0 || >=3 || >=4 || >=5 || >=6" } }, "node_modules/@auth/prisma-adapter/node_modules/@auth/core": { - "version": "0.41.1", - "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.1.tgz", - "integrity": "sha512-t9cJ2zNYAdWMacGRMT6+r4xr1uybIdmYa49calBPeTqwgAFPV/88ac9TEvCR85pvATiSPt8VaNf+Gt24JIT/uw==", + "version": "0.41.2", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.2.tgz", + "integrity": "sha512-Hx5MNBxN2fJTbJKGUKAA0wca43D0Akl3TvufY54Gn8lop7F+34vU1zA1pn0vQfIoVuLIrpfc2nkyjwIaPJMW7w==", "license": "ISC", "dependencies": { "@panva/hkdf": "^1.2.1", @@ -123,18 +205,18 @@ } }, "node_modules/@auth/prisma-adapter/node_modules/jose": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", - "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } }, "node_modules/@auth/prisma-adapter/node_modules/oauth4webapi": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.3.tgz", - "integrity": "sha512-pQ5BsX3QRTgnt5HxgHwgunIRaDXBdkT23tf8dfzmtTIL2LTpdmxgbpbBm0VgFWAIDlezQvQCTgnVIUmHupXHxw==", + "version": "3.8.6", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.6.tgz", + "integrity": "sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -150,90 +232,57 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@chevrotain/cst-dts-gen": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", - "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "10.5.0", - "@chevrotain/types": "10.5.0", - "lodash": "4.17.21" - } - }, - "node_modules/@chevrotain/gast": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", - "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "10.5.0", - "lodash": "4.17.21" - } - }, - "node_modules/@chevrotain/types": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", - "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/utils": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", - "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==", - "license": "Apache-2.0" - }, "node_modules/@electric-sql/pglite": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.2.tgz", - "integrity": "sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", + "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", "license": "Apache-2.0" }, "node_modules/@electric-sql/pglite-socket": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.0.6.tgz", - "integrity": "sha512-6RjmgzphIHIBA4NrMGJsjNWK4pu+bCWJlEWlwcxFTVY3WT86dFpKwbZaGWZV6C5Rd7sCk1Z0CI76QEfukLAUXw==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.1.1.tgz", + "integrity": "sha512-p2hoXw3Z3LQHwTeikdZNsFBOvXGqKY2hk51BBw+8NKND8eoH+8LFOtW9Z8CQKmTJ2qqGYu82ipqiyFZOTTXNfw==", "license": "Apache-2.0", "bin": { "pglite-server": "dist/scripts/server.js" }, "peerDependencies": { - "@electric-sql/pglite": "0.3.2" + "@electric-sql/pglite": "0.4.1" } }, "node_modules/@electric-sql/pglite-tools": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.2.7.tgz", - "integrity": "sha512-9dAccClqxx4cZB+Ar9B+FZ5WgxDc/Xvl9DPrTWv+dYTf0YNubLzi4wHHRGRGhrJv15XwnyKcGOZAP1VXSneSUg==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.3.1.tgz", + "integrity": "sha512-C+T3oivmy9bpQvSxVqXA1UDY8cB9Eb9vZHL9zxWwEUfDixbXv4G3r2LjoTdR33LD8aomR3O9ZXEO3XEwr/cUCA==", "license": "Apache-2.0", "peerDependencies": { - "@electric-sql/pglite": "0.3.2" + "@electric-sql/pglite": "0.4.1" } }, "node_modules/@emnapi/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", - "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.1.0", + "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", "optional": true, @@ -242,9 +291,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", "optional": true, @@ -252,10 +301,17 @@ "tslib": "^2.4.0" } }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "dev": true, + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", "cpu": [ "ppc64" ], @@ -270,9 +326,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", "cpu": [ "arm" ], @@ -287,9 +343,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", "cpu": [ "arm64" ], @@ -304,9 +360,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", "cpu": [ "x64" ], @@ -321,9 +377,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", "cpu": [ "arm64" ], @@ -338,9 +394,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", "cpu": [ "x64" ], @@ -355,9 +411,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", "cpu": [ "arm64" ], @@ -372,9 +428,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", "cpu": [ "x64" ], @@ -389,9 +445,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", "cpu": [ "arm" ], @@ -406,9 +462,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", "cpu": [ "arm64" ], @@ -423,9 +479,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", "cpu": [ "ia32" ], @@ -440,9 +496,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", "cpu": [ "loong64" ], @@ -457,9 +513,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", "cpu": [ "mips64el" ], @@ -474,9 +530,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", "cpu": [ "ppc64" ], @@ -491,9 +547,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", "cpu": [ "riscv64" ], @@ -508,9 +564,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", "cpu": [ "s390x" ], @@ -525,9 +581,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", "cpu": [ "x64" ], @@ -542,9 +598,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", "cpu": [ "arm64" ], @@ -559,9 +615,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", "cpu": [ "x64" ], @@ -576,9 +632,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", "cpu": [ "arm64" ], @@ -593,9 +649,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", "cpu": [ "x64" ], @@ -610,9 +666,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", "cpu": [ "arm64" ], @@ -627,9 +683,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", "cpu": [ "x64" ], @@ -644,9 +700,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", "cpu": [ "arm64" ], @@ -661,9 +717,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", "cpu": [ "ia32" ], @@ -678,9 +734,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", "cpu": [ "x64" ], @@ -695,9 +751,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -748,9 +804,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -759,9 +815,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -782,31 +838,31 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", - "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.10" + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", - "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.3", - "@floating-ui/utils": "^0.2.10" + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", - "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.4" + "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", @@ -814,9 +870,9 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, "node_modules/@google/generative-ai": { @@ -829,9 +885,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.6.tgz", - "integrity": "sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw==", + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", + "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -857,9 +913,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -868,9 +924,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -906,6 +962,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -923,6 +980,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -932,12 +990,13 @@ } }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -985,60 +1044,40 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@mrleebo/prisma-ast": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@mrleebo/prisma-ast/-/prisma-ast-0.12.1.tgz", - "integrity": "sha512-JwqeCQ1U3fvccttHZq7Tk0m/TMC6WcFAQZdukypW3AzlJYKYTGNVd1ANU2GuhKnv4UQuOFj3oAl0LLG/gxFN1w==", - "license": "MIT", - "dependencies": { - "chevrotain": "^10.5.0", - "lilconfig": "^2.1.0" - }, - "engines": { - "node": ">=16" - } + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" }, "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, "node_modules/@neondatabase/serverless": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-1.0.2.tgz", - "integrity": "sha512-I5sbpSIAHiB+b6UttofhrN/UJXII+4tZPAq1qugzwCwLIL8EZLV7F/JyHUrEIiGgQpEXzpnjlJ+zwcEhheGvCw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-1.1.0.tgz", + "integrity": "sha512-r3ZZhRjEcfEdKIZnoB1RusNgvHuaBRqfCzV4Gi+5A9yUX0S4HTws/ASWqt13wL4y4I+0rqsWGdA2w7EQXHi3+Q==", "license": "MIT", - "dependencies": { - "@types/node": "^22.15.30", - "@types/pg": "^8.8.0" - }, "engines": { "node": ">=19.0.0" } }, - "node_modules/@neondatabase/serverless/node_modules/@types/node": { - "version": "22.19.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", - "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@neondatabase/serverless/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "license": "MIT" - }, "node_modules/@next/env": { "version": "14.2.35", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.35.tgz", @@ -1260,6 +1299,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, "license": "MIT", "optional": true, "engines": { @@ -1267,34 +1307,35 @@ } }, "node_modules/@prisma/adapter-neon": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/adapter-neon/-/adapter-neon-7.2.0.tgz", - "integrity": "sha512-XLTCQ0mDdaOqzBO99jyMOWBofJ5iqsvgWYB7lW+2ZEXvY6888tkeKfpo2VwdKhu1Cj/q4IqHd15eFQzPwbpjJQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-neon/-/adapter-neon-7.8.0.tgz", + "integrity": "sha512-9MoOjRuY53sQNyji87u1NivsK0tIacoUE3PqTxdG95b7ORn1pzwribH2z83j4Vh3eb9nQ/UC+MTfVW7jCFnXlA==", "license": "Apache-2.0", "dependencies": { "@neondatabase/serverless": ">0.6.0 <2", - "@prisma/driver-adapter-utils": "7.2.0", + "@prisma/driver-adapter-utils": "7.8.0", "postgres-array": "3.0.4" } }, "node_modules/@prisma/adapter-pg": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.2.0.tgz", - "integrity": "sha512-euIdQ13cRB2wZ3jPsnDnFhINquo1PYFPCg6yVL8b2rp3EdinQHsX9EDdCtRr489D5uhphcRk463OdQAFlsCr0w==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.8.0.tgz", + "integrity": "sha512-ygb3UkerK3v8MDpXVgCISdRNDozpxh6+JVJgiIGbSr5KBgz10LLf5ejUskPGoXlsIjxsOu6nuy1JVQr2EKGSlg==", "license": "Apache-2.0", "dependencies": { - "@prisma/driver-adapter-utils": "7.2.0", + "@prisma/driver-adapter-utils": "7.8.0", + "@types/pg": "^8.16.0", "pg": "^8.16.3", "postgres-array": "3.0.4" } }, "node_modules/@prisma/client": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.2.0.tgz", - "integrity": "sha512-JdLF8lWZ+LjKGKpBqyAlenxd/kXjd1Abf/xK+6vUA7R7L2Suo6AFTHFRpPSdAKCan9wzdFApsUpSa/F6+t1AtA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.8.0.tgz", + "integrity": "sha512-HFp3Dawv/3sU3JtlPha90IB+48lS7zHiH4LKZPjmcE8YH5P9DOXGPvo8dqOtO7MqLDd1p2hOWMcFlRT1DMblHw==", "license": "Apache-2.0", "dependencies": { - "@prisma/client-runtime-utils": "7.2.0" + "@prisma/client-runtime-utils": "7.8.0" }, "engines": { "node": "^20.19 || ^22.12 || >=24.0" @@ -1313,143 +1354,255 @@ } }, "node_modules/@prisma/client-runtime-utils": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.2.0.tgz", - "integrity": "sha512-dn7oB53v0tqkB0wBdMuTNFNPdEbfICEUe82Tn9FoKAhJCUkDH+fmyEp0ClciGh+9Hp2Tuu2K52kth2MTLstvmA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.8.0.tgz", + "integrity": "sha512-5NQZztQ0oY/ADFkmd9gPuweH5A1/CCY8YQPorLLO0Mu6a87mY5gsnDkzmFmIHs9NFaLnZojzgddFVN4RpKYrdw==", "license": "Apache-2.0" }, "node_modules/@prisma/config": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.2.0.tgz", - "integrity": "sha512-qmvSnfQ6l/srBW1S7RZGfjTQhc44Yl3ldvU6y3pgmuLM+83SBDs6UQVgMtQuMRe9J3gGqB0RF8wER6RlXEr6jQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.8.0.tgz", + "integrity": "sha512-HFESzd9rx2ZQxlK+TL7tu1HPvCqrHiL6LCxYykI2c34mvaUuIVVl3lYuicJD/MNnzgPnyeBEMlK4WTomJCV5jw==", "license": "Apache-2.0", "dependencies": { - "c12": "3.1.0", + "c12": "3.3.4", "deepmerge-ts": "7.1.5", - "effect": "3.18.4", + "effect": "3.20.0", "empathic": "2.0.0" } }, "node_modules/@prisma/debug": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz", - "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.8.0.tgz", + "integrity": "sha512-p+QZReysDUqXC+mk17q9a+Y/qzh4c2KYliDK30buYUyfrGeTGSyfmc0AIrJRhZJrLHhRiJa9Au/J72h3C+szvA==", "license": "Apache-2.0" }, "node_modules/@prisma/dev": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.17.0.tgz", - "integrity": "sha512-6sGebe5jxX+FEsQTpjHLzvOGPn6ypFQprcs3jcuIWv1Xp/5v6P/rjfdvAwTkP2iF6pDx2tCd8vGLNWcsWzImTA==", + "version": "0.24.3", + "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.24.3.tgz", + "integrity": "sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg==", "license": "ISC", "dependencies": { - "@electric-sql/pglite": "0.3.2", - "@electric-sql/pglite-socket": "0.0.6", - "@electric-sql/pglite-tools": "0.2.7", - "@hono/node-server": "1.19.6", - "@mrleebo/prisma-ast": "0.12.1", - "@prisma/get-platform": "6.8.2", - "@prisma/query-plan-executor": "6.18.0", + "@electric-sql/pglite": "0.4.1", + "@electric-sql/pglite-socket": "0.1.1", + "@electric-sql/pglite-tools": "0.3.1", + "@hono/node-server": "1.19.11", + "@prisma/get-platform": "7.2.0", + "@prisma/query-plan-executor": "7.2.0", + "@prisma/streams-local": "0.1.2", "foreground-child": "3.3.1", - "get-port-please": "3.1.2", - "hono": "4.10.6", + "get-port-please": "3.2.0", + "hono": "^4.12.8", "http-status-codes": "2.3.0", "pathe": "2.0.3", "proper-lockfile": "4.1.2", - "remeda": "2.21.3", - "std-env": "3.9.0", + "remeda": "2.33.4", + "std-env": "3.10.0", "valibot": "1.2.0", - "zeptomatch": "2.0.2" + "zeptomatch": "2.1.0" } }, "node_modules/@prisma/driver-adapter-utils": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.2.0.tgz", - "integrity": "sha512-gzrUcbI9VmHS24Uf+0+7DNzdIw7keglJsD5m/MHxQOU68OhGVzlphQRobLiDMn8CHNA2XN8uugwKjudVtnfMVQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.8.0.tgz", + "integrity": "sha512-/Q13o0ZT0rjc1Xk0Q9KhZYwuq2EW/vSbWUBKfgEKkaCuB/Sg6bqnjmTZqC5cD4d6y1vfFAEwBRzfzoSMIVJ55A==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.2.0" + "@prisma/debug": "7.8.0" } }, "node_modules/@prisma/engines": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.2.0.tgz", - "integrity": "sha512-HUeOI/SvCDsHrR9QZn24cxxZcujOjcS3w1oW/XVhnSATAli5SRMOfp/WkG3TtT5rCxDA4xOnlJkW7xkho4nURA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.8.0.tgz", + "integrity": "sha512-jx3rCnNNrt5uzbkKlegtQ2GZHxSlihMCzutgT/BP6UIDF1r9tDI39hV/0T/cHZgzJ3ELbuQPXlVZy+Y1n0pcgw==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.2.0", - "@prisma/engines-version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3", - "@prisma/fetch-engine": "7.2.0", - "@prisma/get-platform": "7.2.0" + "@prisma/debug": "7.8.0", + "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", + "@prisma/fetch-engine": "7.8.0", + "@prisma/get-platform": "7.8.0" } }, "node_modules/@prisma/engines-version": { - "version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3.tgz", - "integrity": "sha512-KezsjCZDsbjNR7SzIiVlUsn9PnLePI7r5uxABlwL+xoerurZTfgQVbIjvjF2sVr3Uc0ZcsnREw3F84HvbggGdA==", + "version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a.tgz", + "integrity": "sha512-fJPQxCkLgA5EayWaW8eArgCvjJ+N+Kz3VyeNKMEeYiQC4alNkxRKFVAGxv/ZUzuJISKqdw+zGeDbS6mn6RCPOA==", "license": "Apache-2.0" }, "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", - "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz", + "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.2.0" + "@prisma/debug": "7.8.0" } }, "node_modules/@prisma/fetch-engine": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.2.0.tgz", - "integrity": "sha512-Z5XZztJ8Ap+wovpjPD2lQKnB8nWFGNouCrglaNFjxIWAGWz0oeHXwUJRiclIoSSXN/ptcs9/behptSk8d0Yy6w==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.8.0.tgz", + "integrity": "sha512-gwB0Euiz/DDRyxFRpLXYlK3RfaZUj1c5dAYMuhZYfApg7arknJlcb9bIsOHDppJmbqYaVA+yBIiFMDBfprsNPQ==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.2.0", - "@prisma/engines-version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3", - "@prisma/get-platform": "7.2.0" + "@prisma/debug": "7.8.0", + "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", + "@prisma/get-platform": "7.8.0" } }, "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", - "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz", + "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.2.0" + "@prisma/debug": "7.8.0" } }, "node_modules/@prisma/get-platform": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.8.2.tgz", - "integrity": "sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", + "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.8.2" + "@prisma/debug": "7.2.0" } }, "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.8.2.tgz", - "integrity": "sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz", + "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==", "license": "Apache-2.0" }, "node_modules/@prisma/query-plan-executor": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-6.18.0.tgz", - "integrity": "sha512-jZ8cfzFgL0jReE1R10gT8JLHtQxjWYLiQ//wHmVYZ2rVkFHoh0DT8IXsxcKcFlfKN7ak7k6j0XMNn2xVNyr5cA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz", + "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==", "license": "Apache-2.0" }, + "node_modules/@prisma/streams-local": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@prisma/streams-local/-/streams-local-0.1.2.tgz", + "integrity": "sha512-l49yTxKKF2odFxaAXTmwmkBKL3+bVQ1tFOooGifu4xkdb9NMNLxHj27XAhTylWZod8I+ISGM5erU1xcl/oBCtg==", + "license": "Apache-2.0", + "dependencies": { + "ajv": "^8.12.0", + "better-result": "^2.7.0", + "env-paths": "^3.0.0", + "proper-lockfile": "^4.1.2" + }, + "engines": { + "bun": ">=1.3.6", + "node": ">=22.0.0" + } + }, + "node_modules/@prisma/streams-local/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@prisma/streams-local/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/@prisma/studio-core": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.9.0.tgz", - "integrity": "sha512-xA2zoR/ADu/NCSQuriBKTh6Ps4XjU0bErkEcgMfnSGh346K1VI7iWKnoq1l2DoxUqiddPHIEWwtxJ6xCHG6W7g==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.27.3.tgz", + "integrity": "sha512-AADjNFPdsrglxHQVTmHFqv6DuKQZ5WY4p5/gVFY017twvNrSwpLJ9lqUbYYxEu2W7nbvVxTZA8deJ8LseNALsw==", "license": "Apache-2.0", + "dependencies": { + "@radix-ui/react-toggle": "1.1.10", + "chart.js": "4.5.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0", + "pnpm": "8" + }, "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, + "node_modules/@prisma/studio-core/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@prisma/studio-core/node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@prisma/studio-core/node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", @@ -2713,9 +2866,9 @@ "license": "MIT" }, "node_modules/@reduxjs/toolkit": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", - "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", + "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -2739,9 +2892,9 @@ } }, "node_modules/@reduxjs/toolkit/node_modules/immer": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz", - "integrity": "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==", + "version": "11.1.8", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.8.tgz", + "integrity": "sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==", "license": "MIT", "funding": { "type": "opencollective", @@ -2756,9 +2909,9 @@ "license": "MIT" }, "node_modules/@rushstack/eslint-patch": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.15.0.tgz", - "integrity": "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz", + "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==", "dev": true, "license": "MIT" }, @@ -2791,9 +2944,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dev": true, "license": "MIT", "optional": true, @@ -2808,6 +2961,14 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/@types/d3": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", @@ -3016,9 +3177,9 @@ "license": "MIT" }, "node_modules/@types/d3-shape": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", - "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", "license": "MIT", "dependencies": { "@types/d3-path": "*" @@ -3062,18 +3223,18 @@ } }, "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", "dependencies": { "@types/ms": "*" } }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "license": "MIT" }, "node_modules/@types/estree-jsx": { @@ -3141,18 +3302,18 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", - "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": ">=7.24.0 <7.24.7" } }, "node_modules/@types/pg": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.16.0.tgz", - "integrity": "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -3167,9 +3328,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "version": "18.3.29", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.29.tgz", + "integrity": "sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==", "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -3404,15 +3565,15 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", "license": "ISC" }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", "cpu": [ "arm" ], @@ -3424,9 +3585,9 @@ ] }, "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", "cpu": [ "arm64" ], @@ -3438,9 +3599,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", "cpu": [ "arm64" ], @@ -3452,9 +3613,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", "cpu": [ "x64" ], @@ -3466,9 +3627,9 @@ ] }, "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", "cpu": [ "x64" ], @@ -3480,9 +3641,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", "cpu": [ "arm" ], @@ -3494,9 +3655,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", "cpu": [ "arm" ], @@ -3508,9 +3669,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", "cpu": [ "arm64" ], @@ -3522,9 +3683,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", "cpu": [ "arm64" ], @@ -3535,10 +3696,38 @@ "linux" ] }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", "cpu": [ "ppc64" ], @@ -3550,9 +3739,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", "cpu": [ "riscv64" ], @@ -3564,9 +3753,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", "cpu": [ "riscv64" ], @@ -3578,9 +3767,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", "cpu": [ "s390x" ], @@ -3592,9 +3781,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", "cpu": [ "x64" ], @@ -3606,9 +3795,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", "cpu": [ "x64" ], @@ -3619,10 +3808,24 @@ "linux" ] }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", "cpu": [ "wasm32" ], @@ -3630,16 +3833,18 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", "cpu": [ "arm64" ], @@ -3651,9 +3856,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", "cpu": [ "ia32" ], @@ -3665,9 +3870,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", "cpu": [ "x64" ], @@ -3679,9 +3884,9 @@ ] }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -3702,18 +3907,21 @@ } }, "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", + "dependencies": { + "debug": "4" + }, "engines": { - "node": ">= 14" + "node": ">= 6.0.0" } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -3731,6 +3939,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3740,6 +3949,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -4002,9 +4212,9 @@ "license": "MIT" }, "node_modules/autoprefixer": { - "version": "10.4.23", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", - "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", "dev": true, "funding": [ { @@ -4022,8 +4232,8 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001760", + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -4064,9 +4274,9 @@ } }, "node_modules/axe-core": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz", - "integrity": "sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==", + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.4.tgz", + "integrity": "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==", "dev": true, "license": "MPL-2.0", "engines": { @@ -4074,14 +4284,15 @@ } }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" } }, "node_modules/axobject-query": { @@ -4108,6 +4319,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -4131,13 +4343,16 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.11", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", - "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "version": "2.10.31", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.31.tgz", + "integrity": "sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/bcrypt": { @@ -4163,6 +4378,12 @@ "bcrypt": "bin/bcrypt" } }, + "node_modules/better-result": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.9.2.tgz", + "integrity": "sha512-WIFoBPCdnTOdk9inkE1ZRvCZ4P0CpSkAiLlchC65N7n9DcjZ3NhqkBOlafzpOVnO8ixyi37kicmSJ3ENhPZl7Q==", + "license": "MIT" + }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", @@ -4186,9 +4407,10 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4208,9 +4430,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -4228,11 +4450,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -4259,26 +4481,26 @@ } }, "node_modules/c12": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", - "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", - "license": "MIT", - "dependencies": { - "chokidar": "^4.0.3", - "confbox": "^0.2.2", - "defu": "^6.1.4", - "dotenv": "^16.6.1", - "exsolve": "^1.0.7", - "giget": "^2.0.0", - "jiti": "^2.4.2", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", + "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.4", + "defu": "^6.1.6", + "dotenv": "^17.3.1", + "exsolve": "^1.0.8", + "giget": "^3.2.0", + "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", - "perfect-debounce": "^1.0.0", - "pkg-types": "^2.2.0", - "rc9": "^2.1.2" + "perfect-debounce": "^2.1.0", + "pkg-types": "^2.3.0", + "rc9": "^3.0.1" }, "peerDependencies": { - "magicast": "^0.3.5" + "magicast": "*" }, "peerDependenciesMeta": { "magicast": { @@ -4286,16 +4508,28 @@ } } }, + "node_modules/c12/node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" }, "engines": { @@ -4356,9 +4590,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001762", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz", - "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==", + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", "funding": [ { "type": "opencollective", @@ -4442,44 +4676,33 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/chevrotain": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", - "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", - "license": "Apache-2.0", + "node_modules/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "license": "MIT", "dependencies": { - "@chevrotain/cst-dts-gen": "10.5.0", - "@chevrotain/gast": "10.5.0", - "@chevrotain/types": "10.5.0", - "@chevrotain/utils": "10.5.0", - "lodash": "4.17.21", - "regexp-to-ast": "0.5.0" + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" } }, "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "readdirp": "^5.0.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">= 20.19.0" }, "funding": { "url": "https://paulmillr.com/funding/" } }, - "node_modules/citty": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", - "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", - "license": "MIT", - "dependencies": { - "consola": "^3.2.3" - } - }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -4511,6 +4734,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -4523,6 +4747,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -4564,20 +4789,11 @@ "license": "MIT" }, "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", "license": "MIT" }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -4587,6 +4803,24 @@ "node": ">= 0.6" } }, + "node_modules/cross-env": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", + "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4826,9 +5060,9 @@ } }, "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", "license": "ISC", "engines": { "node": ">=12" @@ -5115,9 +5349,9 @@ "license": "MIT" }, "node_modules/decode-named-character-reference": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", - "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "license": "MIT", "dependencies": { "character-entities": "^2.0.0" @@ -5180,15 +5414,15 @@ } }, "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", "license": "MIT" }, "node_modules/delaunator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", - "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", "license": "ISC", "dependencies": { "robust-predicates": "^3.0.2" @@ -5316,6 +5550,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, "license": "MIT" }, "node_modules/ecdsa-sig-formatter": { @@ -5328,9 +5563,9 @@ } }, "node_modules/effect": { - "version": "3.18.4", - "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", - "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz", + "integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==", "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -5338,9 +5573,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.267", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", - "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "version": "1.5.360", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.360.tgz", + "integrity": "sha512-GkcBt6YYAw9SxFWn+xVar4cLVGlXVuswwtRLBozi2zp0GjXs4ZnOrqV4zbXzg35n7w81hCkyJNYicgXlVHAmBA==", "dev": true, "license": "ISC" }, @@ -5348,6 +5583,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, "license": "MIT" }, "node_modules/empathic": { @@ -5371,10 +5607,22 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "dev": true, "license": "MIT", "dependencies": { @@ -5459,16 +5707,16 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", - "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", + "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.24.1", + "es-abstract": "^1.24.2", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", @@ -5480,7 +5728,7 @@ "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", - "safe-array-concat": "^1.1.3" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -5545,9 +5793,9 @@ } }, "node_modules/es-toolkit": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.43.0.tgz", - "integrity": "sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==", + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz", + "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==", "license": "MIT", "workspaces": [ "docs", @@ -5555,9 +5803,9 @@ ] }, "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -5568,32 +5816,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, "node_modules/escalade": { @@ -5740,15 +5988,15 @@ } }, "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", "dev": true, "license": "MIT", "dependencies": { "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" } }, "node_modules/eslint-import-resolver-node/node_modules/debug": { @@ -5824,9 +6072,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -5858,9 +6106,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -5911,9 +6159,9 @@ } }, "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -5922,9 +6170,9 @@ } }, "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -5981,9 +6229,9 @@ } }, "node_modules/eslint-plugin-react/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -6005,9 +6253,9 @@ } }, "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -6017,24 +6265,6 @@ "node": "*" } }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/eslint-plugin-react/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -6076,9 +6306,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -6087,9 +6317,9 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -6118,9 +6348,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6174,9 +6404,9 @@ } }, "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, "node_modules/exsolve": { @@ -6217,7 +6447,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -6264,6 +6493,22 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -6356,16 +6601,16 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", @@ -6519,33 +6764,39 @@ } }, "node_modules/gaxios": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", - "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2", - "rimraf": "^5.0.1" + "node-fetch": "^3.3.2" }, "engines": { "node": ">=18" } }, - "node_modules/gaxios/node_modules/rimraf": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", - "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", - "license": "ISC", + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" + "agent-base": "^7.1.2", + "debug": "4" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">= 14" } }, "node_modules/gcp-metadata": { @@ -6615,9 +6866,9 @@ } }, "node_modules/get-port-please": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.1.2.tgz", - "integrity": "sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz", + "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==", "license": "MIT" }, "node_modules/get-proto": { @@ -6652,9 +6903,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "dev": true, "license": "MIT", "dependencies": { @@ -6665,18 +6916,10 @@ } }, "node_modules/giget": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", - "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-3.2.0.tgz", + "integrity": "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==", "license": "MIT", - "dependencies": { - "citty": "^0.1.6", - "consola": "^3.4.0", - "defu": "^6.1.4", - "node-fetch-native": "^1.6.6", - "nypm": "^0.6.0", - "pathe": "^2.0.3" - }, "bin": { "giget": "dist/cli.mjs" } @@ -6685,6 +6928,8 @@ "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -6771,17 +7016,16 @@ } }, "node_modules/google-auth-library": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", - "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^7.0.0", - "gcp-metadata": "^8.0.0", - "google-logging-utils": "^1.0.0", - "gtoken": "^8.0.0", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", "jws": "^4.0.0" }, "engines": { @@ -6828,18 +7072,11 @@ "dev": true, "license": "MIT" }, - "node_modules/gtoken": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", - "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", - "license": "MIT", - "dependencies": { - "gaxios": "^7.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=18" - } + "node_modules/graphmatch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz", + "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==", + "license": "MIT" }, "node_modules/has-bigints": { "version": "1.1.0", @@ -6921,9 +7158,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -7082,9 +7319,9 @@ } }, "node_modules/hono": { - "version": "4.10.6", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.10.6.tgz", - "integrity": "sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g==", + "version": "4.12.21", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.21.tgz", + "integrity": "sha512-uV63apnb0kyPtAUwoWgaGh9HyIFcv8lgmzPZSiTBQAFOFGIzka5EZ1dZocmGnn0XdX0+XTqJ6Tqv7selMuGLRQ==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -7117,16 +7354,16 @@ "license": "MIT" }, "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", + "agent-base": "6", "debug": "4" }, "engines": { - "node": ">= 14" + "node": ">= 6" } }, "node_modules/iconv-lite": { @@ -7369,13 +7606,13 @@ } }, "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "hasown": "^2.0.3" }, "engines": { "node": ">= 0.4" @@ -7459,6 +7696,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7768,6 +8006,7 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -7783,9 +8022,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" @@ -7966,12 +8205,16 @@ } }, "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lines-and-columns": { @@ -7997,12 +8240,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -8081,15 +8318,21 @@ } }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } }, "node_modules/lru.min": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.3.tgz", - "integrity": "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", "license": "MIT", "engines": { "bun": ">=1.0.0", @@ -8158,9 +8401,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -9023,6 +9266,7 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -9045,10 +9289,11 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -9080,9 +9325,9 @@ } }, "node_modules/mysql2/node_modules/iconv-lite": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", - "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -9120,9 +9365,9 @@ } }, "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", @@ -9211,9 +9456,9 @@ } }, "node_modules/next-auth": { - "version": "4.24.13", - "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.13.tgz", - "integrity": "sha512-sgObCfcfL7BzIK76SS5TnQtc3yo2Oifp/yIpfv6fMfeBOiBJkDWF3A2y9+yqnmJ4JKc2C+nMjSjmgDeTwgN1rQ==", + "version": "4.24.14", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.14.tgz", + "integrity": "sha512-YRz6xFDXKUwiXSMMChbrBEWyFktZ1qZXEgeSHQQ3nsy08B4c/xLk6REeutRsIFwkjY/1+ShHnu07DN3JeJguig==", "license": "ISC", "dependencies": { "@babel/runtime": "^7.20.13", @@ -9271,9 +9516,9 @@ } }, "node_modules/node-addon-api": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", - "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz", + "integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==", "license": "MIT", "engines": { "node": "^18 || ^20 || >= 21" @@ -9299,6 +9544,35 @@ "node": ">=10.5.0" } }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/node-fetch": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", @@ -9317,12 +9591,6 @@ "url": "https://opencollective.com/node-fetch" } }, - "node_modules/node-fetch-native": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", - "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", - "license": "MIT" - }, "node_modules/node-gyp-build": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", @@ -9335,11 +9603,14 @@ } }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.45", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.45.tgz", + "integrity": "sha512-iIbHXV9eBB2nB0wa7oTsrrXq+qQt+9SIlx9AX3T96YgobtEQfis5n6TJ6vV+3QP8DwdriEAcGhARaFCu37peBg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/normalize-path": { "version": "3.0.0", @@ -9351,31 +9622,23 @@ "node": ">=0.10.0" } }, - "node_modules/nypm": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", - "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", - "license": "MIT", - "dependencies": { - "citty": "^0.1.6", - "consola": "^3.4.2", - "pathe": "^2.0.3", - "pkg-types": "^2.3.0", - "tinyexec": "^1.0.1" - }, - "bin": { - "nypm": "dist/cli.mjs" - }, - "engines": { - "node": "^14.16.0 || >=16.10.0" - } - }, "node_modules/oauth": { "version": "0.9.15", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", "license": "MIT" }, + "node_modules/oauth4webapi": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.17.0.tgz", + "integrity": "sha512-lbC0Z7uzAFNFyzEYRIC+pkSVvDHJTbEW+dYlSBAlCYDe6RxUkJ26bClhk8ocBZip1wfI9uKTe0fm4Ib4RHn6uQ==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -9387,10 +9650,9 @@ } }, "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", "license": "MIT", "engines": { "node": ">= 6" @@ -9549,27 +9811,6 @@ "url": "https://github.com/sponsors/panva" } }, - "node_modules/openid-client/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/openid-client/node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -9728,6 +9969,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -9740,6 +9982,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -9757,20 +10006,20 @@ "license": "MIT" }, "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", "license": "MIT" }, "node_modules/pg": { - "version": "8.16.3", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", - "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", + "integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==", "license": "MIT", "dependencies": { - "pg-connection-string": "^2.9.1", - "pg-pool": "^3.10.1", - "pg-protocol": "^1.10.3", + "pg-connection-string": "^2.13.0", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.14.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, @@ -9778,7 +10027,7 @@ "node": ">= 16.0.0" }, "optionalDependencies": { - "pg-cloudflare": "^1.2.7" + "pg-cloudflare": "^1.4.0" }, "peerDependencies": { "pg-native": ">=3.0.1" @@ -9790,16 +10039,16 @@ } }, "node_modules/pg-cloudflare": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", - "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", "license": "MIT", "optional": true }, "node_modules/pg-connection-string": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", - "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz", + "integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==", "license": "MIT" }, "node_modules/pg-int8": { @@ -9812,18 +10061,18 @@ } }, "node_modules/pg-pool": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", - "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", "license": "MIT", "peerDependencies": { "pg": ">=8.0" } }, "node_modules/pg-protocol": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", - "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz", + "integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==", "license": "MIT" }, "node_modules/pg-types": { @@ -9867,9 +10116,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -9900,13 +10149,13 @@ } }, "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", "license": "MIT", "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", + "confbox": "^0.2.4", + "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, @@ -9921,9 +10170,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -9941,7 +10190,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -9967,6 +10216,28 @@ "postcss": "^8.0.0" } }, + "node_modules/postcss-import/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/postcss-js": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", @@ -10036,19 +10307,6 @@ } } }, - "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, "node_modules/postcss-nested": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", @@ -10181,9 +10439,9 @@ } }, "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", "bin": { @@ -10203,16 +10461,16 @@ "license": "MIT" }, "node_modules/prisma": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.2.0.tgz", - "integrity": "sha512-jSdHWgWOgFF24+nRyyNRVBIgGDQEsMEF8KPHvhBBg3jWyR9fUAK0Nq9ThUmiGlNgq2FA7vSk/ZoCvefod+a8qg==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.8.0.tgz", + "integrity": "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/config": "7.2.0", - "@prisma/dev": "0.17.0", - "@prisma/engines": "7.2.0", - "@prisma/studio-core": "0.9.0", + "@prisma/config": "7.8.0", + "@prisma/dev": "0.24.3", + "@prisma/engines": "7.8.0", + "@prisma/studio-core": "0.27.3", "mysql2": "3.15.3", "postgres": "3.4.7" }, @@ -10282,10 +10540,13 @@ } }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/punycode": { "version": "2.3.1", @@ -10335,13 +10596,13 @@ "license": "MIT" }, "node_modules/rc9": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", - "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz", + "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==", "license": "MIT", "dependencies": { - "defu": "^6.1.4", - "destr": "^2.0.3" + "defu": "^6.1.6", + "destr": "^2.0.5" } }, "node_modules/react": { @@ -10370,9 +10631,9 @@ } }, "node_modules/react-is": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz", - "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", + "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", "license": "MIT", "peer": true }, @@ -10404,9 +10665,9 @@ } }, "node_modules/react-redux": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", - "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", + "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", "license": "MIT", "dependencies": { "@types/use-sync-external-store": "^0.0.6", @@ -10506,12 +10767,12 @@ } }, "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", "license": "MIT", "engines": { - "node": ">= 14.18.0" + "node": ">= 20.19.0" }, "funding": { "type": "individual", @@ -10519,15 +10780,15 @@ } }, "node_modules/recharts": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.6.0.tgz", - "integrity": "sha512-L5bjxvQRAe26RlToBAziKUB7whaGKEwD3znoM6fz3DrTowCIC/FnJYnuq1GEzB8Zv2kdTfaxQfi5GoH0tBinyg==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz", + "integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==", "license": "MIT", "workspaces": [ "www" ], "dependencies": { - "@reduxjs/toolkit": "1.x.x || 2.x.x", + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", @@ -10586,12 +10847,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexp-to-ast": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", - "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", - "license": "MIT" - }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -10709,24 +10964,21 @@ } }, "node_modules/remeda": { - "version": "2.21.3", - "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.21.3.tgz", - "integrity": "sha512-XXrZdLA10oEOQhLLzEJEiFFSKi21REGAkHdImIb4rt/XXy8ORGXh5HCcpUOsElfPNDb+X6TA/+wkh+p2KffYmg==", + "version": "2.33.4", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz", + "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==", "license": "MIT", - "dependencies": { - "type-fest": "^4.39.1" + "funding": { + "url": "https://github.com/sponsors/remeda" } }, - "node_modules/remeda/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "license": "(MIT OR CC0-1.0)", + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, "node_modules/reselect": { @@ -10736,13 +10988,16 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.1", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -10814,9 +11069,9 @@ } }, "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -10828,7 +11083,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -10847,9 +11102,9 @@ } }, "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -10860,9 +11115,9 @@ } }, "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", "license": "Unlicense" }, "node_modules/run-parallel": { @@ -10896,15 +11151,15 @@ "license": "BSD-3-Clause" }, "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, @@ -10986,9 +11241,9 @@ } }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -11093,14 +11348,14 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -11225,9 +11480,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "license": "MIT" }, "node_modules/stop-iteration-iterator": { @@ -11256,6 +11511,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -11274,6 +11530,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -11288,12 +11545,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -11303,12 +11562,13 @@ } }, "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -11448,6 +11708,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -11461,6 +11722,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -11593,9 +11855,9 @@ } }, "node_modules/tailwind-merge": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", - "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz", + "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==", "license": "MIT", "funding": { "type": "github", @@ -11698,17 +11960,14 @@ "jiti": "bin/jiti.js" } }, - "node_modules/tailwindcss/node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "node_modules/tailwindcss/node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "dev": true, "license": "MIT", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" + "node": ">= 6" } }, "node_modules/tailwindcss/node_modules/readdirp": { @@ -11724,6 +11983,28 @@ "node": ">=8.10.0" } }, + "node_modules/tailwindcss/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -11760,24 +12041,15 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, - "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -11805,9 +12077,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -11890,14 +12162,13 @@ "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==", + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", + "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" + "esbuild": "~0.28.0" }, "bin": { "tsx": "dist/cli.mjs" @@ -12047,9 +12318,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "license": "MIT" }, "node_modules/unified": { @@ -12111,9 +12382,9 @@ } }, "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -12140,38 +12411,41 @@ } }, "node_modules/unrs-resolver": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "napi-postinstall": "^0.3.0" + "napi-postinstall": "^0.3.4" }, "funding": { "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" } }, "node_modules/update-browserslist-db": { @@ -12278,6 +12552,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", "license": "MIT", "bin": { "uuid": "dist/bin/uuid" @@ -12463,9 +12738,9 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", "dev": true, "license": "MIT", "dependencies": { @@ -12498,6 +12773,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -12516,6 +12792,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -12533,12 +12810,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -12553,6 +12832,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -12565,6 +12845,7 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -12574,12 +12855,13 @@ } }, "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -12596,9 +12878,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -12645,12 +12927,13 @@ } }, "node_modules/zeptomatch": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.0.2.tgz", - "integrity": "sha512-H33jtSKf8Ijtb5BW6wua3G5DhnFjbFML36eFu+VdOoVY4HD9e7ggjqdM6639B+L87rjnR6Y+XeRzBXZdy52B/g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz", + "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==", "license": "MIT", "dependencies": { - "grammex": "^3.1.10" + "grammex": "^3.1.11", + "graphmatch": "^1.1.0" } }, "node_modules/zwitch": { diff --git a/package.json b/package.json index dbf29097..fb2779c5 100644 --- a/package.json +++ b/package.json @@ -6,23 +6,25 @@ "node": "22.x" }, "scripts": { - "dev": "NODE_OPTIONS=\"--dns-result-order=ipv4first --no-network-family-autoselection\" next dev", - "build": "prisma generate && next build", - "typecheck": "prisma generate && rm -f tsconfig.tsbuildinfo && tsc -p tsconfig.json --noEmit", - "build:worker": "tsc -p tsconfig.worker.json", - "preworker": "node -e \"const fs=require('fs'); const cp=require('child_process'); if(fs.existsSync('node_modules/.bin/tsc')){cp.execSync('npm run -s build:worker',{stdio:'inherit'});} else {console.log('Skipping build:worker (tsc not installed)');}\"", - "preworker:server": "node -e \"const fs=require('fs'); const cp=require('child_process'); if(fs.existsSync('node_modules/.bin/tsc')){cp.execSync('npm run -s build:worker',{stdio:'inherit'});} else {console.log('Skipping build:worker (tsc not installed)');}\"", - "start": "NODE_OPTIONS=\"--dns-result-order=ipv4first --no-network-family-autoselection\" next start -p $PORT", - "worker": "NODE_OPTIONS=\"--dns-result-order=ipv4first --no-network-family-autoselection\" node dist-worker/scripts/analysisWorker.js", - "worker:server": "NODE_OPTIONS=\"--dns-result-order=ipv4first --no-network-family-autoselection\" node dist-worker/scripts/workerServer.js", - "worker:dev": "tsx scripts/analysisWorker.ts", - "lint": "next lint", - "format": "prettier --write \"**/*.{ts,tsx,css}\"", - "prisma:generate": "prisma generate", - "prisma:migrate": "prisma migrate dev", - "prisma:studio": "prisma studio", - "test:validation": "tsx scripts/test-validation.ts" - }, + "dev": "cross-env NODE_OPTIONS=\"--dns-result-order=ipv4first --no-network-family-autoselection\" next dev", + "build": "prisma generate && next build", + "typecheck": "prisma generate && rm -f tsconfig.tsbuildinfo && tsc -p tsconfig.json --noEmit", + "build:worker": "tsc -p tsconfig.worker.json", + "preworker": "node -e \"const fs=require('fs'); const cp=require('child_process'); if(fs.existsSync('node_modules/.bin/tsc')){cp.execSync('npm run -s build:worker',{stdio:'inherit'});} else {console.log('Skipping build:worker (tsc not installed)');}\"", + "preworker:server": "node -e \"const fs=require('fs'); const cp=require('child_process'); if(fs.existsSync('node_modules/.bin/tsc')){cp.execSync('npm run -s build:worker',{stdio:'inherit'});} else {console.log('Skipping build:worker (tsc not installed)');}\"", + "start": "cross-env NODE_OPTIONS=\"--dns-result-order=ipv4first --no-network-family-autoselection\" next start -p $PORT", + + "worker": "cross-env NODE_OPTIONS=\"--dns-result-order=ipv4first --no-network-family-autoselection\" node dist-worker/scripts/analysisWorker.js", + +"worker:server": "cross-env NODE_OPTIONS=\"--dns-result-order=ipv4first --no-network-family-autoselection\" node dist-worker/scripts/workerServer.js", + "worker:dev": "tsx scripts/analysisWorker.ts", + "lint": "next lint", + "format": "prettier --write \"**/*.{ts,tsx,css}\"", + "prisma:generate": "prisma generate", + "prisma:migrate": "prisma migrate dev", + "prisma:studio": "prisma studio", + "test:validation": "tsx scripts/test-validation.ts" +}, "dependencies": { "@auth/prisma-adapter": "^2.11.1", "@google/generative-ai": "^0.24.1", @@ -71,6 +73,7 @@ "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^6.14.0", "autoprefixer": "^10.4.16", + "cross-env": "^10.1.0", "eslint": "^8.55.0", "eslint-config-next": "^14.1.0", "postcss": "^8.4.32", diff --git a/src/components/ui/Skeleton.tsx b/src/components/ui/Skeleton.tsx index 40d075b5..e5e3bdcd 100644 --- a/src/components/ui/Skeleton.tsx +++ b/src/components/ui/Skeleton.tsx @@ -1,5 +1,7 @@ -import { cn } from "@/utils/cn"; - +// simple classnames helper to avoid depending on '@/utils/helpers' +function cn(...classes: Array) { + return classes.filter(Boolean).join(" "); +} function Skeleton({ className, ...props From 8e8e99713fee37a246c5fe4354c5e2d99740b15e Mon Sep 17 00:00:00 2001 From: Prateek Jha Date: Fri, 22 May 2026 04:52:31 +0530 Subject: [PATCH 070/123] docs: add manual repro steps for worker validation --- scripts/WORKER_REPRO.md | 49 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 scripts/WORKER_REPRO.md diff --git a/scripts/WORKER_REPRO.md b/scripts/WORKER_REPRO.md new file mode 100644 index 00000000..714dcb22 --- /dev/null +++ b/scripts/WORKER_REPRO.md @@ -0,0 +1,49 @@ +Manual repro for worker POST /run endpoint + +This file documents how to exercise the new validation and expected responses. + +Prerequisite: the server is running (it starts when you run the script normally): + +Windows PowerShell / cmd example: + +```bash +node dist/workerServer.js +# or when running ts-node in dev +npx ts-node scripts/workerServer.ts +``` + +1. Valid request (starts background worker loop) + +```bash +curl -X POST http://localhost:8080/run \ + -H "Content-Type: application/json" \ + -d '{"once": true, "workerId": "test-worker-1", "pollIntervalMs": 2000}' + +# Expected response: 202 with JSON {"status":"started"} +``` + +2. Invalid JSON + +```bash +curl -X POST http://localhost:8080/run \ + -H "Content-Type: application/json" \ + -d '{invalid json' + +# Expected response: 400 with JSON {"error":"Invalid JSON body"} +``` + +3. Invalid input types (validation error) + +```bash +curl -X POST http://localhost:8080/run \ + -H "Content-Type: application/json" \ + -d '{"once": "no", "pollIntervalMs": -5}' + +# Expected response: 400 with JSON {"error": "once must be a boolean; pollIntervalMs must be a positive integer"} +``` + +Notes + +- Error responses never include stack traces; they return concise JSON `error` messages. +- The endpoint expects `application/json` Content-Type. +- Use the `once` flag to run one pass (`true`) or omit to run the regular long-lived loop. From 694706e062a63854d7e65fc6ac1b0668869205b5 Mon Sep 17 00:00:00 2001 From: Prateek Jha Date: Fri, 22 May 2026 05:03:33 +0530 Subject: [PATCH 071/123] fix(worker): validate inputs and improve error handling for /run endpoint --- scripts/workerServer.ts | 112 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/scripts/workerServer.ts b/scripts/workerServer.ts index 534ccf9d..7f3204f4 100644 --- a/scripts/workerServer.ts +++ b/scripts/workerServer.ts @@ -7,6 +7,7 @@ const port = Number(process.env.PORT || "8080"); function startHealthServer() { const server = http.createServer((req, res) => { + // health endpoints if (req.url === "/" || req.url === "/healthz" || req.url === "/readyz") { res.statusCode = 200; res.setHeader("content-type", "text/plain; charset=utf-8"); @@ -14,6 +15,63 @@ function startHealthServer() { return; } + // Control endpoint: start a worker run with validated input + if (req.method === "POST" && req.url === "/run") { + (async () => { + try { + const ct = String(req.headers["content-type"] || "").toLowerCase(); + if (!ct.startsWith("application/json")) { + res.statusCode = 400; + res.setHeader("content-type", "application/json; charset=utf-8"); + res.end(JSON.stringify({ error: "Content-Type must be application/json" })); + return; + } + + let body = ""; + for await (const chunk of req) { + body += chunk; + if (body.length > 1_000_000) break; // guard + } + + let parsed: any; + try { + parsed = body ? JSON.parse(body) : {}; + } catch (e) { + res.statusCode = 400; + res.setHeader("content-type", "application/json; charset=utf-8"); + res.end(JSON.stringify({ error: "Invalid JSON body" })); + return; + } + + const { ok, errors, opts } = validateRunOptions(parsed); + if (!ok) { + res.statusCode = 400; + res.setHeader("content-type", "application/json; charset=utf-8"); + res.end(JSON.stringify({ error: errors.join("; ") })); + return; + } + + // Start the worker loop in background. Do not expose internal errors to clients. + void startAnalysisWorkerLoop(opts).catch((e) => + console.error("/run: failed to start worker loop", e) + ); + + res.statusCode = 202; + res.setHeader("content-type", "application/json; charset=utf-8"); + res.end(JSON.stringify({ status: "started" })); + return; + } catch (e: any) { + // Unexpected server error: return generic message without stack + console.error("/run: unexpected error", e); + res.statusCode = 500; + res.setHeader("content-type", "application/json; charset=utf-8"); + res.end(JSON.stringify({ error: "internal server error" })); + return; + } + })(); + return; + } + res.statusCode = 404; res.setHeader("content-type", "text/plain; charset=utf-8"); res.end("not found"); @@ -24,6 +82,58 @@ function startHealthServer() { }); } +function validateRunOptions(input: any): { + ok: boolean; + errors: string[]; + opts?: { + workerId?: string; + pollIntervalMs?: number; + heartbeatIntervalMs?: number; + lockMs?: number; + once?: boolean; + }; +} { + const errors: string[] = []; + const opts: any = {}; + + if (input == null || typeof input !== "object") { + errors.push("body must be a JSON object"); + return { ok: false, errors }; + } + + if ("workerId" in input) { + if (typeof input.workerId !== "string" || input.workerId.trim() === "") { + errors.push("workerId must be a non-empty string"); + } else { + opts.workerId = input.workerId.trim(); + } + } + + if ("once" in input) { + if (typeof input.once !== "boolean") errors.push("once must be a boolean"); + else opts.once = input.once; + } + + const intFields: Array<"pollIntervalMs" | "heartbeatIntervalMs" | "lockMs"> = [ + "pollIntervalMs", + "heartbeatIntervalMs", + "lockMs", + ]; + + for (const f of intFields) { + if (f in input) { + const n = Number(input[f]); + if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) { + errors.push(`${f} must be a positive integer`); + } else { + opts[f] = n; + } + } + } + + return { ok: errors.length === 0, errors, opts }; +} + async function main() { startHealthServer(); @@ -34,4 +144,4 @@ async function main() { main().catch((e) => { console.error("worker-server fatal:", e); process.exit(1); -}); +}); \ No newline at end of file From 6b29d1cae5bbf1c004f645a35f5bc2b0bfcbe6ba Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Fri, 22 May 2026 09:07:14 +0530 Subject: [PATCH 072/123] feat(worker): add empty state UI for analysis jobs Resolves #126 by converting the placeholder analysis job page into a polished empty state using the existing design system. --- app/analysis/[jobId]/page.tsx | 44 ++++++------------- .../repository/RepositoryOverview.tsx | 1 + 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/app/analysis/[jobId]/page.tsx b/app/analysis/[jobId]/page.tsx index 8229409f..2f018ba0 100644 --- a/app/analysis/[jobId]/page.tsx +++ b/app/analysis/[jobId]/page.tsx @@ -1,40 +1,24 @@ "use client"; import { useRouter } from "next/navigation"; +import { DashboardLayout } from "@/components/layout/DashboardLayout"; +import { EmptyState } from "@/components/ui"; +import { Activity } from "lucide-react"; export default function AnalysisJobPage() { const router = useRouter(); return ( -
-

- No Analysis Jobs Found -

-

- You haven't created any analysis jobs yet. -

- -
+ +
+ router.push("/analyze")} + /> +
+
); } diff --git a/src/components/repository/RepositoryOverview.tsx b/src/components/repository/RepositoryOverview.tsx index 33d9b853..efbcc5ec 100644 --- a/src/components/repository/RepositoryOverview.tsx +++ b/src/components/repository/RepositoryOverview.tsx @@ -24,6 +24,7 @@ import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import rehypeRaw from "rehype-raw"; import rehypeSanitize, { defaultSchema } from "rehype-sanitize"; +import { EmptyState } from "@/components/ui/EmptyState"; interface RepositoryData { id: string; From a6581598936c7356b4c5fb84144c3df1eebd0800 Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Fri, 22 May 2026 09:35:30 +0530 Subject: [PATCH 073/123] feat(ui): add primary call-to-action to users empty state --- src/components/repository/Contributors.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/repository/Contributors.tsx b/src/components/repository/Contributors.tsx index f52c2157..46521bbe 100644 --- a/src/components/repository/Contributors.tsx +++ b/src/components/repository/Contributors.tsx @@ -130,6 +130,8 @@ export function Contributors({ repository }: ContributorsProps) { icon={Users} title="No contributors found" description="We couldn't find any contributor activity in this repository." + actionLabel="Refresh Data" + onAction={() => window.location.reload()} /> ) : ( <> From ccfa6d1968e6e02b952c46bf471fe4b656dc8bc0 Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Fri, 22 May 2026 10:44:45 +0530 Subject: [PATCH 074/123] fix: handle rate limits and retries for user integrations - Add withRetry and getRetryDelayMs utilities to shared rateLimit.ts - Update GitHub service to retry 429 with backoff before throwing - Add rate limit detection, retry, and token sanitization to GitLab service - Add rate limit detection, retry, and token sanitization to Bitbucket service - Add error sanitization to GitHubAppService to redact tokens - Sanitize console.error across all integration and user routes - Add PRIVATE-TOKEN pattern to log sanitizer for GitLab --- .../integrations/github/app/callback/route.ts | 3 +- .../integrations/github/app/delete/route.ts | 3 +- .../github/app/install-url/route.ts | 3 +- app/api/integrations/github/app/sync/route.ts | 3 +- app/api/integrations/github/connect/route.ts | 3 +- .../github/connected-repos/route.ts | 3 +- app/api/integrations/github/import/route.ts | 3 +- .../integrations/github/repositories/route.ts | 3 +- .../integrations/github/select-repos/route.ts | 3 +- app/api/users/me/route.ts | 5 +- app/api/users/profile/route.ts | 3 +- app/api/webhooks/github/route.ts | 3 +- lib/services/bitbucketService.ts | 77 +++++++++++++++- lib/services/githubAppService.ts | 75 ++++++++++------ lib/services/githubService.ts | 45 ++++++---- lib/services/gitlabService.ts | 90 +++++++++++++++++-- lib/utils/rateLimit.ts | 49 ++++++++++ 17 files changed, 313 insertions(+), 61 deletions(-) diff --git a/app/api/integrations/github/app/callback/route.ts b/app/api/integrations/github/app/callback/route.ts index d8978355..efb34193 100644 --- a/app/api/integrations/github/app/callback/route.ts +++ b/app/api/integrations/github/app/callback/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; import prisma from "@/lib/prisma"; import { GitHubAppService } from "@/lib/services/githubAppService"; import { GitHubService, GitHubRateLimitError } from "@/lib/services/githubService"; +import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; import { verifySignedState } from "@/lib/utils/signedState"; type InstallState = { userId: number; ts: number; nonce?: string }; @@ -125,7 +126,7 @@ export async function GET(request: NextRequest) { redirectUrl.searchParams.set("installation_id", String(installationId)); return NextResponse.redirect(redirectUrl); } catch (e: any) { - console.error("GitHub App callback error:", e); + console.error("GitHub App callback error:", sanitizeErrorMessage(e)); if (e instanceof GitHubRateLimitError) { redirectUrl.searchParams.set("install", "error"); diff --git a/app/api/integrations/github/app/delete/route.ts b/app/api/integrations/github/app/delete/route.ts index 73e471e2..583af447 100644 --- a/app/api/integrations/github/app/delete/route.ts +++ b/app/api/integrations/github/app/delete/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; import prisma from "@/lib/prisma"; import { isHttpError, requireAuth } from "@/lib/middleware"; import { GitHubAppService } from "@/lib/services/githubAppService"; +import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; export async function POST(request: NextRequest) { try { @@ -84,7 +85,7 @@ export async function POST(request: NextRequest) { { status: 200 }, ); } catch (error: any) { - console.error("GitHub App delete error:", error); + console.error("GitHub App delete error:", sanitizeErrorMessage(error)); if (isHttpError(error)) { return NextResponse.json( { error: error.message }, diff --git a/app/api/integrations/github/app/install-url/route.ts b/app/api/integrations/github/app/install-url/route.ts index a4bd727e..42cb4c39 100644 --- a/app/api/integrations/github/app/install-url/route.ts +++ b/app/api/integrations/github/app/install-url/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import { isHttpError, requireAuth } from "@/lib/middleware"; +import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; import { createSignedState } from "@/lib/utils/signedState"; function getRequiredEnv(name: string): string { @@ -25,7 +26,7 @@ export async function POST(request: NextRequest) { return NextResponse.json({ url }, { status: 200 }); } catch (error: any) { - console.error("GitHub App install-url error:", error); + console.error("GitHub App install-url error:", sanitizeErrorMessage(error)); if (isHttpError(error)) { return NextResponse.json( { error: error.message }, diff --git a/app/api/integrations/github/app/sync/route.ts b/app/api/integrations/github/app/sync/route.ts index 5e437e02..5f2c57c9 100644 --- a/app/api/integrations/github/app/sync/route.ts +++ b/app/api/integrations/github/app/sync/route.ts @@ -3,6 +3,7 @@ import { isHttpError, requireAuth } from "@/lib/middleware"; import prisma from "@/lib/prisma"; import { GitHubAppService } from "@/lib/services/githubAppService"; import { GitHubService, GitHubRateLimitError } from "@/lib/services/githubService"; +import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; export async function POST(request: NextRequest) { try { @@ -108,7 +109,7 @@ export async function POST(request: NextRequest) { { status: 200 }, ); } catch (error: any) { - console.error("GitHub App sync error:", error); + console.error("GitHub App sync error:", sanitizeErrorMessage(error)); if (error instanceof GitHubRateLimitError) { return NextResponse.json( diff --git a/app/api/integrations/github/connect/route.ts b/app/api/integrations/github/connect/route.ts index 1495cf98..99024d87 100644 --- a/app/api/integrations/github/connect/route.ts +++ b/app/api/integrations/github/connect/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; import { isHttpError, requireAuth } from "@/lib/middleware"; import prisma from "@/lib/prisma"; import { GitHubService, GitHubRateLimitError } from "@/lib/services/githubService"; +import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; import { toJsonSafe } from "@/lib/utils/jsonSafe"; export async function POST(request: NextRequest) { @@ -45,7 +46,7 @@ export async function POST(request: NextRequest) { return NextResponse.json({ account: toJsonSafe(account) }, { status: 200 }); } catch (error: any) { - console.error("GitHub connect error:", error); + console.error("GitHub connect error:", sanitizeErrorMessage(error)); if (error instanceof GitHubRateLimitError) { return NextResponse.json( diff --git a/app/api/integrations/github/connected-repos/route.ts b/app/api/integrations/github/connected-repos/route.ts index 1f6c567f..524a9295 100644 --- a/app/api/integrations/github/connected-repos/route.ts +++ b/app/api/integrations/github/connected-repos/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import { isHttpError, requireAuth } from "@/lib/middleware"; import prisma from "@/lib/prisma"; +import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; import { toJsonSafe } from "@/lib/utils/jsonSafe"; export async function GET(request: NextRequest) { @@ -34,7 +35,7 @@ export async function GET(request: NextRequest) { { status: 200 }, ); } catch (error: any) { - console.error("GitHub connected repos error:", error); + console.error("GitHub connected repos error:", sanitizeErrorMessage(error)); if (isHttpError(error)) { return NextResponse.json( { error: error.message }, diff --git a/app/api/integrations/github/import/route.ts b/app/api/integrations/github/import/route.ts index 792ddfcc..5adb54db 100644 --- a/app/api/integrations/github/import/route.ts +++ b/app/api/integrations/github/import/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import { requireAuth } from "@/lib/middleware"; import { GitHubService, GitHubRateLimitError } from "@/lib/services/githubService"; +import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; import { repositoryService } from "@/lib/services/repositoryService"; export async function POST(request: NextRequest) { @@ -43,7 +44,7 @@ export async function POST(request: NextRequest) { return NextResponse.json({ repository, source: "github" }, { status: 201 }); } catch (error: any) { - console.error("GitHub import error:", error); + console.error("GitHub import error:", sanitizeErrorMessage(error)); if (error instanceof GitHubRateLimitError) { return NextResponse.json( diff --git a/app/api/integrations/github/repositories/route.ts b/app/api/integrations/github/repositories/route.ts index 003c285d..206a7bc8 100644 --- a/app/api/integrations/github/repositories/route.ts +++ b/app/api/integrations/github/repositories/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import { isHttpError, requireAuth } from "@/lib/middleware"; import { GitHubService, GitHubRateLimitError } from "@/lib/services/githubService"; +import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; import prisma from "@/lib/prisma"; export async function POST(request: NextRequest) { @@ -54,7 +55,7 @@ export async function POST(request: NextRequest) { return NextResponse.json({ repositories, source: "github-app-db" }); } catch (error: any) { - console.error("GitHub repositories error:", error); + console.error("GitHub repositories error:", sanitizeErrorMessage(error)); if (error instanceof GitHubRateLimitError) { return NextResponse.json( diff --git a/app/api/integrations/github/select-repos/route.ts b/app/api/integrations/github/select-repos/route.ts index 8e43f3d2..862505cb 100644 --- a/app/api/integrations/github/select-repos/route.ts +++ b/app/api/integrations/github/select-repos/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import { isHttpError, requireAuth } from "@/lib/middleware"; import prisma from "@/lib/prisma"; +import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; import { toJsonSafe } from "@/lib/utils/jsonSafe"; import { GitHubRateLimitError } from "@/lib/services/githubService"; @@ -67,7 +68,7 @@ export async function POST(request: NextRequest) { return NextResponse.json({ repos: toJsonSafe(repos) }, { status: 200 }); } catch (error: any) { - console.error("GitHub select repos error:", error); + console.error("GitHub select repos error:", sanitizeErrorMessage(error)); if (error instanceof GitHubRateLimitError) { return NextResponse.json( diff --git a/app/api/users/me/route.ts b/app/api/users/me/route.ts index 02d01429..9522db71 100644 --- a/app/api/users/me/route.ts +++ b/app/api/users/me/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import prisma from "@/lib/prisma"; import { requireAuth } from "@/lib/middleware"; +import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; export const dynamic = "force-dynamic"; export const runtime = "nodejs"; @@ -39,7 +40,7 @@ export async function GET(request: NextRequest) { isGoogleLinked: hasGoogleAccount, }); } catch (error: any) { - console.error("Error fetching user:", error); + console.error("Error fetching user:", sanitizeErrorMessage(error)); return NextResponse.json( { message: "Failed to fetch user" }, { status: 500 } @@ -57,7 +58,7 @@ export async function DELETE(request: NextRequest) { return NextResponse.json({ message: "Account deleted" }); } catch (error: any) { - console.error("Error deleting account:", error); + console.error("Error deleting account:", sanitizeErrorMessage(error)); if (error?.code === "P2025") { return NextResponse.json({ message: "User not found" }, { status: 404 }); } diff --git a/app/api/users/profile/route.ts b/app/api/users/profile/route.ts index 6fa13827..a69d258a 100644 --- a/app/api/users/profile/route.ts +++ b/app/api/users/profile/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import prisma from "@/lib/prisma"; import { requireAuth } from "@/lib/middleware"; +import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; import bcrypt from "bcryptjs"; export async function PUT(request: NextRequest) { @@ -96,7 +97,7 @@ export async function PUT(request: NextRequest) { avatarUrl: (updatedUser as any).image, }); } catch (error: any) { - console.error("Error updating profile:", error); + console.error("Error updating profile:", sanitizeErrorMessage(error)); return NextResponse.json( { message: "Failed to update profile" }, { status: 500 } diff --git a/app/api/webhooks/github/route.ts b/app/api/webhooks/github/route.ts index 5cbfb86a..766818b8 100644 --- a/app/api/webhooks/github/route.ts +++ b/app/api/webhooks/github/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; import { verifyGitHubWebhookSignature } from "@/lib/utils/githubWebhook"; import { GitHubAppService } from "@/lib/services/githubAppService"; import { GitHubService } from "@/lib/services/githubService"; +import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; import prisma from "@/lib/prisma"; import { formatPRReviewMarkdown, @@ -265,7 +266,7 @@ export async function POST(request: NextRequest) { }, }); } catch (error: any) { - console.error("GitHub webhook PR review error:", error); + console.error("GitHub webhook PR review error:", sanitizeErrorMessage(error)); return noStoreResponse( { error: "Failed to process PR webhook" }, 500 diff --git a/lib/services/bitbucketService.ts b/lib/services/bitbucketService.ts index b91b6ebf..2022d740 100644 --- a/lib/services/bitbucketService.ts +++ b/lib/services/bitbucketService.ts @@ -1,4 +1,34 @@ -import axios, { AxiosInstance } from 'axios' +import axios, { AxiosInstance, isAxiosError } from 'axios' +import { getRetryDelayMs } from '@/lib/utils/rateLimit' + +export class BitbucketRateLimitError extends Error { + retryAfterSeconds: number + constructor(retryAfterSeconds: number) { + super( + retryAfterSeconds > 0 + ? `Bitbucket API rate limit reached. Please retry after ${retryAfterSeconds} seconds.` + : 'Bitbucket API rate limit reached. Please try again later.', + ) + this.name = 'BitbucketRateLimitError' + this.retryAfterSeconds = retryAfterSeconds + } +} + +function sanitizeBitbucketError(error: any) { + if (isAxiosError(error) && error.config) { + const safeConfig = { + ...error.config, + headers: error.config.headers + ? { + ...error.config.headers, + Authorization: '[REDACTED]', + } + : error.config.headers, + } + error.config = safeConfig as any + } + return error +} export interface BitbucketRepository { uuid: string @@ -35,6 +65,51 @@ export class BitbucketService { ...(token && { Authorization: `Bearer ${token}` }), }, }) + + this.client.interceptors.response.use( + (response) => response, + async (error) => { + if (!isAxiosError(error) || !error.config) { + throw sanitizeBitbucketError(error) + } + + const status = error.response?.status + const config = error.config as any + config.retryCount = config.retryCount || 0 + + if (status === 429) { + if (config.retryCount >= 3) { + const retryAfterHeader = error.response?.headers?.['retry-after'] + const retrySeconds = retryAfterHeader + ? Math.max(1, parseInt(retryAfterHeader, 10)) + : 60 + throw new BitbucketRateLimitError(retrySeconds) + } + config.retryCount += 1 + const delayMs = getRetryDelayMs(error, config.retryCount) ?? 1000 + await new Promise((resolve) => setTimeout(resolve, delayMs)) + return this.client(config) + } + + const retryableCodes = [502, 503, 504] + if ( + (status && retryableCodes.includes(status)) || + error.code === 'ECONNABORTED' || + error.code === 'ECONNRESET' || + error.code === 'ETIMEDOUT' || + !error.response + ) { + if (config.retryCount < 3) { + config.retryCount += 1 + const backoff = Math.pow(2, config.retryCount) * 1000 + Math.random() * 1000 + await new Promise((resolve) => setTimeout(resolve, backoff)) + return this.client(config) + } + } + + throw sanitizeBitbucketError(error) + }, + ) } /** diff --git a/lib/services/githubAppService.ts b/lib/services/githubAppService.ts index db1a331c..558f7778 100644 --- a/lib/services/githubAppService.ts +++ b/lib/services/githubAppService.ts @@ -1,4 +1,4 @@ -import axios from "axios"; +import axios, { isAxiosError } from "axios"; import jwt from "jsonwebtoken"; function getRequiredEnv(name: string): string { @@ -10,10 +10,25 @@ function getRequiredEnv(name: string): string { } function normalizePrivateKey(value: string): string { - // Common deployment pattern: store multiline key with literal "\n". return value.includes("\\n") ? value.replace(/\\n/g, "\n") : value; } +function sanitizeAppError(error: any) { + if (isAxiosError(error) && error.config) { + const safeConfig = { + ...error.config, + headers: error.config.headers + ? { + ...error.config.headers, + Authorization: "[REDACTED]", + } + : error.config.headers, + }; + error.config = safeConfig as any; + } + return error; +} + export class GitHubAppService { private appId: string; private privateKey: string; @@ -29,7 +44,7 @@ export class GitHubAppService { const now = Math.floor(Date.now() / 1000); const payload = { iat: now - 60, - exp: now + 9 * 60, // max 10 minutes + exp: now + 9 * 60, iss: this.appId, }; @@ -42,23 +57,27 @@ export class GitHubAppService { } const appJwt = this.createAppJwt(); - const response = await axios.post( - `https://api.github.com/app/installations/${installationId}/access_tokens`, - {}, - { - headers: { - Authorization: `Bearer ${appJwt}`, - Accept: "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", + try { + const response = await axios.post( + `https://api.github.com/app/installations/${installationId}/access_tokens`, + {}, + { + headers: { + Authorization: `Bearer ${appJwt}`, + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }, }, - }, - ); + ); - const token = response.data?.token as string | undefined; - if (!token) { - throw new Error("Failed to obtain installation access token"); + const token = response.data?.token as string | undefined; + if (!token) { + throw new Error("Failed to obtain installation access token"); + } + return token; + } catch (err) { + throw sanitizeAppError(err); } - return token; } async uninstallInstallation(installationId: number): Promise { @@ -67,15 +86,19 @@ export class GitHubAppService { } const appJwt = this.createAppJwt(); - await axios.delete( - `https://api.github.com/app/installations/${installationId}`, - { - headers: { - Authorization: `Bearer ${appJwt}`, - Accept: "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", + try { + await axios.delete( + `https://api.github.com/app/installations/${installationId}`, + { + headers: { + Authorization: `Bearer ${appJwt}`, + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }, }, - }, - ); + ); + } catch (err) { + throw sanitizeAppError(err); + } } } diff --git a/lib/services/githubService.ts b/lib/services/githubService.ts index 18787da7..7c55d9dd 100644 --- a/lib/services/githubService.ts +++ b/lib/services/githubService.ts @@ -1,9 +1,14 @@ import axios, { AxiosError, AxiosInstance, isAxiosError } from "axios"; +import { getRetryDelayMs } from "@/lib/utils/rateLimit"; export class GitHubRateLimitError extends Error { retryAfterSeconds: number; constructor(retryAfterSeconds: number) { - super(`GitHub API rate limit reached. Please retry after ${retryAfterSeconds} seconds.`); + super( + retryAfterSeconds > 0 + ? `GitHub API rate limit reached. Please retry after ${retryAfterSeconds} seconds.` + : "GitHub API rate limit reached. Please try again later.", + ); this.name = "GitHubRateLimitError"; this.retryAfterSeconds = retryAfterSeconds; } @@ -165,30 +170,40 @@ export class GitHubService { const status = error.response?.status; const config = error.config as any; - if (status === 429 || status === 403) { + config.retryCount = config.retryCount || 0; + + const isRateLimit = status === 429 || status === 403; + if (isRateLimit) { const rateLimitRemaining = error.response?.headers?.["x-ratelimit-remaining"]; if (status === 429 || rateLimitRemaining === "0") { - const retryAfterHeader = error.response?.headers?.["retry-after"]; - const resetHeader = error.response?.headers?.["x-ratelimit-reset"]; - let retrySeconds = 60; - - if (retryAfterHeader) { - retrySeconds = parseInt(retryAfterHeader, 10); - } else if (resetHeader) { - const resetTime = parseInt(resetHeader, 10) * 1000; - retrySeconds = Math.max(1, Math.ceil((resetTime - Date.now()) / 1000)); + if (config.retryCount >= 3) { + const retryAfterHeader = error.response?.headers?.["retry-after"]; + const resetHeader = error.response?.headers?.["x-ratelimit-reset"]; + let retrySeconds = 60; + + if (retryAfterHeader) { + retrySeconds = parseInt(retryAfterHeader, 10); + } else if (resetHeader) { + const resetTime = parseInt(resetHeader, 10) * 1000; + retrySeconds = Math.max(1, Math.ceil((resetTime - Date.now()) / 1000)); + } + throw new GitHubRateLimitError(retrySeconds); } - throw new GitHubRateLimitError(retrySeconds); + config.retryCount += 1; + const delayMs = getRetryDelayMs(error, config.retryCount) ?? 1000; + await new Promise((resolve) => setTimeout(resolve, delayMs)); + return this.client(config); } } - const retryStatusCodes = [502, 503, 504]; + const retryableCodes = [502, 503, 504]; if ( - (status && retryStatusCodes.includes(status)) || + (status && retryableCodes.includes(status)) || error.code === "ECONNABORTED" || + error.code === "ECONNRESET" || + error.code === "ETIMEDOUT" || !error.response ) { - config.retryCount = config.retryCount || 0; if (config.retryCount < 3) { config.retryCount += 1; const backoff = Math.pow(2, config.retryCount) * 1000 + Math.random() * 1000; diff --git a/lib/services/gitlabService.ts b/lib/services/gitlabService.ts index 9c75b69b..fbbc3393 100644 --- a/lib/services/gitlabService.ts +++ b/lib/services/gitlabService.ts @@ -1,4 +1,34 @@ -import axios, { AxiosInstance } from 'axios' +import axios, { AxiosInstance, isAxiosError } from 'axios' +import { getRetryDelayMs } from '@/lib/utils/rateLimit' + +export class GitLabRateLimitError extends Error { + retryAfterSeconds: number + constructor(retryAfterSeconds: number) { + super( + retryAfterSeconds > 0 + ? `GitLab API rate limit reached. Please retry after ${retryAfterSeconds} seconds.` + : 'GitLab API rate limit reached. Please try again later.', + ) + this.name = 'GitLabRateLimitError' + this.retryAfterSeconds = retryAfterSeconds + } +} + +function sanitizeGitLabError(error: any) { + if (isAxiosError(error) && error.config) { + const safeConfig = { + ...error.config, + headers: error.config.headers + ? { + ...error.config.headers, + 'PRIVATE-TOKEN': '[REDACTED]', + } + : error.config.headers, + } + error.config = safeConfig as any + } + return error +} export interface GitLabProject { id: number @@ -40,6 +70,51 @@ export class GitLabService { ...(token && { 'PRIVATE-TOKEN': token }), }, }) + + this.client.interceptors.response.use( + (response) => response, + async (error) => { + if (!isAxiosError(error) || !error.config) { + throw sanitizeGitLabError(error) + } + + const status = error.response?.status + const config = error.config as any + config.retryCount = config.retryCount || 0 + + if (status === 429) { + if (config.retryCount >= 3) { + const retryAfterHeader = error.response?.headers?.['retry-after'] + const retrySeconds = retryAfterHeader + ? Math.max(1, parseInt(retryAfterHeader, 10)) + : 60 + throw new GitLabRateLimitError(retrySeconds) + } + config.retryCount += 1 + const delayMs = getRetryDelayMs(error, config.retryCount) ?? 1000 + await new Promise((resolve) => setTimeout(resolve, delayMs)) + return this.client(config) + } + + const retryableCodes = [502, 503, 504] + if ( + (status && retryableCodes.includes(status)) || + error.code === 'ECONNABORTED' || + error.code === 'ECONNRESET' || + error.code === 'ETIMEDOUT' || + !error.response + ) { + if (config.retryCount < 3) { + config.retryCount += 1 + const backoff = Math.pow(2, config.retryCount) * 1000 + Math.random() * 1000 + await new Promise((resolve) => setTimeout(resolve, backoff)) + return this.client(config) + } + } + + throw sanitizeGitLabError(error) + }, + ) } /** @@ -88,7 +163,7 @@ export class GitLabService { */ async getBranches(projectId: string): Promise { const response = await this.client.get( - `/projects/${encodeURIComponent(projectId)}/repository/branches` + `/projects/${encodeURIComponent(projectId)}/repository/branches`, ) return response.data } @@ -102,7 +177,7 @@ export class GitLabService { ref_name?: string per_page?: number page?: number - } + }, ): Promise { const response = await this.client.get( `/projects/${encodeURIComponent(projectId)}/repository/commits`, @@ -112,7 +187,7 @@ export class GitLabService { per_page: params?.per_page || 100, page: params?.page || 1, }, - } + }, ) return response.data @@ -123,7 +198,7 @@ export class GitLabService { */ async getContributors(projectId: string): Promise { const response = await this.client.get( - `/projects/${encodeURIComponent(projectId)}/repository/contributors` + `/projects/${encodeURIComponent(projectId)}/repository/contributors`, ) return response.data } @@ -132,7 +207,10 @@ export class GitLabService { * Parse GitLab URL */ static parseGitLabUrl(url: string): { projectPath: string } | null { - const patterns = [/gitlab\.com\/([^\/]+\/[^\/]+?)(?:\.git)?$/, /gitlab\.com\/([^\/]+\/[^\/]+)/] + const patterns = [ + /gitlab\.com\/([^\/]+\/[^\/]+?)(?:\.git)?$/, + /gitlab\.com\/([^\/]+\/[^\/]+)/, + ] for (const pattern of patterns) { const match = url.match(pattern) diff --git a/lib/utils/rateLimit.ts b/lib/utils/rateLimit.ts index 178f8742..1a2590a0 100644 --- a/lib/utils/rateLimit.ts +++ b/lib/utils/rateLimit.ts @@ -9,6 +9,54 @@ const RATE_LIMIT_PATTERNS = [ "429", ] as const; +const RETRYABLE_STATUSES = [429, 502, 503, 504]; +const RETRYABLE_CODES = ["ECONNABORTED", "ECONNRESET", "ETIMEDOUT", "ERR_NETWORK"]; +const MAX_RETRIES = 3; + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export async function withRetry( + fn: () => Promise, + options?: { + maxRetries?: number; + onRetry?: (attempt: number, error: unknown, delayMs: number) => void; + }, +): Promise { + const maxRetries = options?.maxRetries ?? MAX_RETRIES; + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await fn(); + } catch (err: unknown) { + if (attempt === maxRetries) throw err; + + const delayMs = getRetryDelayMs(err, attempt); + if (delayMs === null) throw err; + + options?.onRetry?.(attempt, err, delayMs); + await sleep(delayMs); + } + } + + throw new Error("unreachable"); +} + +export function getRetryDelayMs(err: unknown, attempt: number): number | null { + const retryAfter = extractRetryAfter(err); + if (retryAfter != null) return retryAfter * 1000; + + const axiosErr = err instanceof AxiosError ? err : null; + const status = axiosErr?.response?.status; + + if (status === 429) return Math.min(60_000, Math.pow(2, attempt) * 1000); + if (status && RETRYABLE_STATUSES.includes(status)) return Math.pow(2, attempt) * 1000 + Math.random() * 1000; + if (axiosErr?.code && RETRYABLE_CODES.includes(axiosErr.code)) return Math.pow(2, attempt) * 1000; + + return null; +} + export function isRateLimitError(err: unknown): boolean { if (err instanceof AxiosError) { if (err.response?.status === 429) return true; @@ -64,6 +112,7 @@ export function sanitizeErrorMessage(err: unknown): string { function stripSensitive(text: string): string { return text .replace(/Bearer\s+[A-Za-z0-9\-._~+/]+=*/gi, "Bearer [REDACTED]") + .replace(/PRIVATE-TOKEN\s+[A-Za-z0-9\-._~+/]+=*/gi, "PRIVATE-TOKEN [REDACTED]") .replace(/token[=:]\s*[A-Za-z0-9\-._~+/]+=*/gi, "token=[REDACTED]") .replace(/gh[pousr]_[A-Za-z0-9_]{36,}/gi, "[REDACTED]") .replace(/https?:\/\/[^@\s]+:[^@\s]+@/g, "https://[REDACTED]@") From a205243f3e4cadd9afd363589edc60d8456c2afb Mon Sep 17 00:00:00 2001 From: prayas Date: Fri, 22 May 2026 11:04:32 +0530 Subject: [PATCH 075/123] fix: resolve duplicate bcrypt dependencies and optimize types --- package-lock.json | 67 ++----------------- package.json | 1 - .../repository/RepositoryOverview.tsx | 1 + 3 files changed, 6 insertions(+), 63 deletions(-) diff --git a/package-lock.json b/package-lock.json index 37ab9474..ff5e7706 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,6 @@ "@types/d3": "^7.4.3", "@types/pg": "^8.16.0", "axios": "^1.13.2", - "bcrypt": "^6.0.0", "bcryptjs": "^3.0.3", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", @@ -240,11 +239,10 @@ } }, "node_modules/@electric-sql/pglite": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.2.tgz", - "integrity": "sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w==", - "license": "Apache-2.0", - "peer": true + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", + "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", + "license": "Apache-2.0" }, "node_modules/@electric-sql/pglite-socket": { "version": "0.1.1", @@ -1327,7 +1325,6 @@ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.8.0.tgz", "integrity": "sha512-HFp3Dawv/3sU3JtlPha90IB+48lS7zHiH4LKZPjmcE8YH5P9DOXGPvo8dqOtO7MqLDd1p2hOWMcFlRT1DMblHw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@prisma/client-runtime-utils": "7.8.0" }, @@ -3326,7 +3323,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.29.tgz", "integrity": "sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==", "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -3338,7 +3334,6 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -3404,7 +3399,6 @@ "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", @@ -3886,7 +3880,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4353,20 +4346,6 @@ "node": ">=6.0.0" } }, - "node_modules/bcrypt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", - "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-addon-api": "^8.3.0", - "node-gyp-build": "^4.8.4" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/bcryptjs": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", @@ -4447,7 +4426,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -5152,7 +5130,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -5856,7 +5833,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -6026,7 +6002,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -7307,7 +7282,6 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.21.tgz", "integrity": "sha512-uV63apnb0kyPtAUwoWgaGh9HyIFcv8lgmzPZSiTBQAFOFGIzka5EZ1dZocmGnn0XdX0+XTqJ6Tqv7selMuGLRQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -9500,15 +9474,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/node-addon-api": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz", - "integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==", - "license": "MIT", - "engines": { - "node": "^18 || ^20 || >= 21" - } - }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -9576,17 +9541,6 @@ "url": "https://opencollective.com/node-fetch" } }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "license": "MIT", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, "node_modules/node-releases": { "version": "2.0.45", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.45.tgz", @@ -10001,7 +9955,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", "integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.13.0", "pg-pool": "^3.14.0", @@ -10175,7 +10128,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", @@ -10398,7 +10350,6 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -10454,7 +10405,6 @@ "integrity": "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@prisma/config": "7.8.0", "@prisma/dev": "0.24.3", @@ -10599,7 +10549,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -10612,7 +10561,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -10660,7 +10608,6 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", "license": "MIT", - "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -10805,8 +10752,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -12075,7 +12021,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -12161,7 +12106,6 @@ "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.28.0" }, @@ -12285,7 +12229,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index dbf29097..d8ff0031 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "@types/d3": "^7.4.3", "@types/pg": "^8.16.0", "axios": "^1.13.2", - "bcrypt": "^6.0.0", "bcryptjs": "^3.0.3", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", diff --git a/src/components/repository/RepositoryOverview.tsx b/src/components/repository/RepositoryOverview.tsx index 33d9b853..efbcc5ec 100644 --- a/src/components/repository/RepositoryOverview.tsx +++ b/src/components/repository/RepositoryOverview.tsx @@ -24,6 +24,7 @@ import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import rehypeRaw from "rehype-raw"; import rehypeSanitize, { defaultSchema } from "rehype-sanitize"; +import { EmptyState } from "@/components/ui/EmptyState"; interface RepositoryData { id: string; From 5bdd959a141fad2b820b1a89d6ea55bcab37c417 Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Fri, 22 May 2026 11:11:00 +0530 Subject: [PATCH 076/123] feat(docs): add skeleton loaders to reduce layout shift --- app/analysis/[jobId]/page.tsx | 2 +- .../repository/RepositoryOverview.tsx | 1 + .../ui/RepositoryAnalysisSkeleton.tsx | 118 ++++++++++++++++-- 3 files changed, 113 insertions(+), 8 deletions(-) diff --git a/app/analysis/[jobId]/page.tsx b/app/analysis/[jobId]/page.tsx index 8229409f..dd65cad6 100644 --- a/app/analysis/[jobId]/page.tsx +++ b/app/analysis/[jobId]/page.tsx @@ -19,7 +19,7 @@ export default function AnalysisJobPage() { No Analysis Jobs Found

- You haven't created any analysis jobs yet. + You haven't created any analysis jobs yet.

{/* Content skeleton */} -
- - - - - - +
+ {/* Repository Header & Quick Stats */} +
+
+
+
+ + + +
+ +
+ + + +
+
+
+ +
+ {Array.from({ length: 4 }).map((_, i) => ( +
+ +
+ + +
+
+ ))} +
+
+ + {/* Repository Stats Grid */} +
+ {Array.from({ length: 4 }).map((_, i) => ( + + +
+
+ + + +
+ +
+
+
+ ))} +
+ + {/* Breakdown & Activity */} +
+ {/* Language Breakdown */} + + + + + + + {Array.from({ length: 3 }).map((_, i) => ( +
+
+ + +
+ +
+ ))} +
+
+ + {/* Recent Activity */} + + + + + + + {Array.from({ length: 4 }).map((_, i) => ( +
+ +
+ + +
+
+ ))} +
+
+
+ + {/* README */} +
+ + + + + + + + + + + + + + + + +
); From 5a346462a5f93b0c77f3f97470f1c746d8c08018 Mon Sep 17 00:00:00 2001 From: Vijitha14 Date: Fri, 22 May 2026 11:16:30 +0530 Subject: [PATCH 077/123] Adjusted shortcut helper text layout --- src/pages/Dashboard.tsx | 9 +++++---- src/pages/LandingPage.tsx | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 7b86026f..f99951ad 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -68,9 +68,9 @@ export default function Dashboard() { searchRef.current?.focus(); } - if (e.key === "Escape" && !isTyping) { - setRepoUrl(""); - searchRef.current?.blur(); + if(e.key === "Escape" && (document.activeElement === searchRef.current || !isTyping)) { + setRepoUrl(""); + searchRef.current?.blur(); } }; @@ -252,10 +252,11 @@ export default function Dashboard() { {analyzing ? "Analyzing..." : "Analyze Repository"} +

Press "/" to focus search • Esc to clear

-
+
diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx index 9cd3dcae..67086172 100644 --- a/src/pages/LandingPage.tsx +++ b/src/pages/LandingPage.tsx @@ -61,7 +61,7 @@ export default function LandingPage() { searchRef.current?.focus(); } - if (e.key === "Escape" && !isTyping) { + if(e.key === "Escape" && (document.activeElement === searchRef.current || !isTyping)) { setRepoUrl(""); searchRef.current?.blur(); } From 982160847b67b3d9165518b402dccf5cf55533a1 Mon Sep 17 00:00:00 2001 From: Vijitha14 Date: Fri, 22 May 2026 11:34:45 +0530 Subject: [PATCH 078/123] Adjusted shortcut helper text layout --- src/components/ui/ShortcutHint.tsx | 16 ++++++++++++++++ src/pages/Dashboard.tsx | 5 ++--- src/pages/LandingPage.tsx | 2 ++ 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 src/components/ui/ShortcutHint.tsx diff --git a/src/components/ui/ShortcutHint.tsx b/src/components/ui/ShortcutHint.tsx new file mode 100644 index 00000000..fe7df03b --- /dev/null +++ b/src/components/ui/ShortcutHint.tsx @@ -0,0 +1,16 @@ +interface ShortcutHintProps { + className?: string; +} + +export function ShortcutHint({ + className = "", +}: ShortcutHintProps) { + return ( +

+ Press "/" to focus search • Esc to clear +

+ ); +} \ No newline at end of file diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index f99951ad..88665b70 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -4,6 +4,7 @@ export const dynamic = "force-dynamic"; import { useState, useEffect, useRef } from "react"; import { useRouter } from "next/navigation"; +import { ShortcutHint } from "@/components/ui/ShortcutHint"; import { GitBranch, TrendingUp, @@ -253,9 +254,7 @@ export default function Dashboard() { {analyzing ? "Analyzing..." : "Analyze Repository"}
-

- Press "/" to focus search • Esc to clear -

+ diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx index 67086172..a5b1fecb 100644 --- a/src/pages/LandingPage.tsx +++ b/src/pages/LandingPage.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useMemo, useRef, useState } from "react"; import { useRouter } from "next/navigation"; +import { ShortcutHint } from "@/components/ui/ShortcutHint"; import { GitBranch, Network, @@ -433,6 +434,7 @@ export default function LandingPage() {

Demo UI only — real analysis happens after install/sign up.

+ {/* Demos */} From 8eb3787d5e599092bf2294531537a6d5dd70362f Mon Sep 17 00:00:00 2001 From: nishita Date: Fri, 22 May 2026 11:44:08 +0530 Subject: [PATCH 079/123] fix: tighten authorization checks in middleware to prevent cross-user data access --- lib/middleware.ts | 90 +++++++++++++------------------ package-lock.json | 132 ++-------------------------------------------- 2 files changed, 42 insertions(+), 180 deletions(-) diff --git a/lib/middleware.ts b/lib/middleware.ts index f1c0d5bc..abf04a9c 100644 --- a/lib/middleware.ts +++ b/lib/middleware.ts @@ -1,64 +1,48 @@ -import { NextRequest } from "next/server"; -import { verifyToken, JWTPayload } from "./auth"; +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; import { getToken } from "next-auth/jwt"; -export interface AuthenticatedRequest { - user: JWTPayload; -} - -export async function getAuthUser( - request: NextRequest -): Promise { - const authHeader = request.headers.get("authorization"); - - // 1) Existing JWT auth (Authorization: Bearer ...) - if (authHeader && authHeader.startsWith("Bearer ")) { - const token = authHeader.substring(7); - const payload = verifyToken(token); - if (payload) return payload; - } - - // 2) NextAuth session cookie (Google OAuth) +export async function middleware(request: NextRequest) { try { + // Step 1: Get the logged-in user's session token const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET, }); - if (!token?.sub || !token.email) return null; - - const userId = Number(token.sub); - if (!Number.isFinite(userId)) return null; - - return { userId, email: token.email }; - } catch { - return null; - } -} - -export async function requireAuth(request: NextRequest): Promise { - const user = await getAuthUser(request); - - if (!user) { - throw new HttpError(401, "Unauthorized"); - } - return user; -} - -export class HttpError extends Error { - status: number; - - constructor(status: number, message: string) { - super(message); - this.status = status; + // Step 2: If no token, user is not logged in → redirect to login + if (!token) { + return NextResponse.redirect(new URL("/login", request.url)); + } + + const userId = token.sub; // This is the logged-in user's ID + + // Step 3: Get the resource owner ID from the request headers (if provided) + const resourceOwnerId = request.headers.get("x-resource-owner-id"); + + // Step 4: If a resource owner is specified, check it matches the logged-in user + if (resourceOwnerId && resourceOwnerId !== userId) { + // Someone is trying to access another user's data → block them! + return NextResponse.json( + { error: "Forbidden: You do not have access to this resource." }, + { status: 403 } + ); + } + + // Step 5: Everything checks out → allow the request to continue + return NextResponse.next(); + + } catch (error) { + // Step 6: Something went wrong on the server → return 500 error + console.error("Middleware error:", error); + return NextResponse.json( + { error: "Internal Server Error" }, + { status: 500 } + ); } } -export function isHttpError(error: unknown): error is HttpError { - return ( - typeof error === "object" && - error !== null && - "status" in error && - typeof (error as any).status === "number" - ); -} +// This tells Next.js WHICH pages/routes to protect +export const config = { + matcher: ["/api/:path*", "/dashboard/:path*", "/profile/:path*"], +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 37ab9474..7a6ca6ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,87 +81,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@auth/core": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.34.3.tgz", - "integrity": "sha512-jMjY/S0doZnWYNV90x0jmU3B+UcrsfGYnukxYrRbj0CVvGI/MX3JbHsxSrx2d4mbnXaUsqJmAcDfoQWA6r0lOw==", - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "@panva/hkdf": "^1.1.1", - "@types/cookie": "0.6.0", - "cookie": "0.6.0", - "jose": "^5.1.3", - "oauth4webapi": "^2.10.4", - "preact": "10.11.3", - "preact-render-to-string": "5.2.3" - }, - "peerDependencies": { - "@simplewebauthn/browser": "^9.0.1", - "@simplewebauthn/server": "^9.0.2", - "nodemailer": "^7" - }, - "peerDependenciesMeta": { - "@simplewebauthn/browser": { - "optional": true - }, - "@simplewebauthn/server": { - "optional": true - }, - "nodemailer": { - "optional": true - } - } - }, - "node_modules/@auth/core/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@auth/core/node_modules/jose": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", - "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", - "license": "MIT", - "optional": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/@auth/core/node_modules/preact": { - "version": "10.11.3", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", - "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", - "license": "MIT", - "optional": true, - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/@auth/core/node_modules/preact-render-to-string": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", - "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "pretty-format": "^3.8.0" - }, - "peerDependencies": { - "preact": ">=10" - } - }, "node_modules/@auth/prisma-adapter": { "version": "2.11.2", "resolved": "https://registry.npmjs.org/@auth/prisma-adapter/-/prisma-adapter-2.11.2.tgz", @@ -240,11 +159,10 @@ } }, "node_modules/@electric-sql/pglite": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.2.tgz", - "integrity": "sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w==", - "license": "Apache-2.0", - "peer": true + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", + "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", + "license": "Apache-2.0" }, "node_modules/@electric-sql/pglite-socket": { "version": "0.1.1", @@ -1327,7 +1245,6 @@ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.8.0.tgz", "integrity": "sha512-HFp3Dawv/3sU3JtlPha90IB+48lS7zHiH4LKZPjmcE8YH5P9DOXGPvo8dqOtO7MqLDd1p2hOWMcFlRT1DMblHw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@prisma/client-runtime-utils": "7.8.0" }, @@ -2955,14 +2872,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/@types/d3": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", @@ -3326,7 +3235,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.29.tgz", "integrity": "sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==", "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -3338,7 +3246,6 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -3404,7 +3311,6 @@ "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", @@ -3886,7 +3792,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4447,7 +4352,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -5152,7 +5056,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -5856,7 +5759,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -6026,7 +5928,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -7307,7 +7208,6 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.21.tgz", "integrity": "sha512-uV63apnb0kyPtAUwoWgaGh9HyIFcv8lgmzPZSiTBQAFOFGIzka5EZ1dZocmGnn0XdX0+XTqJ6Tqv7selMuGLRQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -9613,17 +9513,6 @@ "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", "license": "MIT" }, - "node_modules/oauth4webapi": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.17.0.tgz", - "integrity": "sha512-lbC0Z7uzAFNFyzEYRIC+pkSVvDHJTbEW+dYlSBAlCYDe6RxUkJ26bClhk8ocBZip1wfI9uKTe0fm4Ib4RHn6uQ==", - "license": "MIT", - "optional": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -10001,7 +9890,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", "integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.13.0", "pg-pool": "^3.14.0", @@ -10175,7 +10063,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", @@ -10398,7 +10285,6 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -10454,7 +10340,6 @@ "integrity": "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@prisma/config": "7.8.0", "@prisma/dev": "0.24.3", @@ -10599,7 +10484,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -10612,7 +10496,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -10660,7 +10543,6 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", "license": "MIT", - "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -10805,8 +10687,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -12075,7 +11956,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -12161,7 +12041,6 @@ "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.28.0" }, @@ -12285,7 +12164,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" From 827f25792f21826571569cf39d9a34d35f82d642 Mon Sep 17 00:00:00 2001 From: Vijitha14 Date: Fri, 22 May 2026 11:52:18 +0530 Subject: [PATCH 080/123] Adjusted shortcut helper text layout --- src/pages/Dashboard.tsx | 4 +++- src/pages/LandingPage.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 88665b70..66bf044b 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -62,7 +62,9 @@ export default function Dashboard() { const isTyping = active instanceof HTMLInputElement || - active instanceof HTMLTextAreaElement; + active instanceof HTMLTextAreaElement || + active instanceof HTMLSelectElement || + (active instanceof HTMLElement && active.isContentEditable); if (e.key === "/" && !isTyping) { e.preventDefault(); diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx index a5b1fecb..3fa4e0f4 100644 --- a/src/pages/LandingPage.tsx +++ b/src/pages/LandingPage.tsx @@ -55,7 +55,9 @@ export default function LandingPage() { const isTyping = active instanceof HTMLInputElement || - active instanceof HTMLTextAreaElement; + active instanceof HTMLTextAreaElement || + active instanceof HTMLSelectElement || + (active instanceof HTMLElement && active.isContentEditable); if (e.key === "/" && !isTyping) { e.preventDefault(); From f16d9451514abbf12da39ccb9ed2de1bfdf0e1f2 Mon Sep 17 00:00:00 2001 From: Vijitha14 Date: Fri, 22 May 2026 12:14:42 +0530 Subject: [PATCH 081/123] Adjusted shortcut helper text layout --- src/pages/Dashboard.tsx | 9 ++++++++- src/pages/LandingPage.tsx | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 66bf044b..9c32429a 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -71,7 +71,14 @@ export default function Dashboard() { searchRef.current?.focus(); } - if(e.key === "Escape" && (document.activeElement === searchRef.current || !isTyping)) { + if ( + e.key === "/" && + !e.ctrlKey && + !e.metaKey && + !e.altKey && + !e.shiftKey && + !isTyping +) { setRepoUrl(""); searchRef.current?.blur(); } diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx index 3fa4e0f4..18ea1f6a 100644 --- a/src/pages/LandingPage.tsx +++ b/src/pages/LandingPage.tsx @@ -59,7 +59,7 @@ export default function LandingPage() { active instanceof HTMLSelectElement || (active instanceof HTMLElement && active.isContentEditable); - if (e.key === "/" && !isTyping) { + if (e.key === "/" && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey && !isTyping) { e.preventDefault(); searchRef.current?.focus(); } From 19e305e2a37c4ee53c2343e9a73bb561d6574237 Mon Sep 17 00:00:00 2001 From: prayas Date: Fri, 22 May 2026 12:25:24 +0530 Subject: [PATCH 082/123] fix: resolve conditional hook execution in commit heatmap --- .../visualizations/CommitActivityHeatmap.tsx | 113 ++++++++---------- 1 file changed, 52 insertions(+), 61 deletions(-) diff --git a/src/components/visualizations/CommitActivityHeatmap.tsx b/src/components/visualizations/CommitActivityHeatmap.tsx index 325d63a4..294d9910 100644 --- a/src/components/visualizations/CommitActivityHeatmap.tsx +++ b/src/components/visualizations/CommitActivityHeatmap.tsx @@ -22,8 +22,10 @@ const generateCommitData = (commits: any[], now: Date): CommitData[] => { // Count commits by date commits?.forEach((commit: any) => { const date = new Date(commit.committedAt || commit.createdAt); - const dateStr = date.toISOString().split("T")[0]; - commitsByDate.set(dateStr, (commitsByDate.get(dateStr) || 0) + 1); + if (!isNaN(date.getTime())) { + const dateStr = date.toISOString().split("T")[0]; + commitsByDate.set(dateStr, (commitsByDate.get(dateStr) || 0) + 1); + } }); // Fill in the last 52 weeks @@ -56,6 +58,29 @@ export function CommitActivityHeatmap({ count: number; } | null>(null); + // ========================================================= + // CRITICAL FIX: EARLY EXTENDED GUARD FOR PERFORMANCE & SAFETY + // ========================================================= + if (!repository?.commits || repository.commits.length === 0) { + return ( + +
+

Commit Activity

+

+ Contribution activity over the last 52 weeks +

+
+
+ +
+
+ ); + } + useEffect(() => { // Advance the window automatically as time passes (refresh at next local midnight). const scheduleNextTick = () => { @@ -77,7 +102,7 @@ export function CommitActivityHeatmap({ useEffect(() => { if (!svgRef.current) return; - const data = generateCommitData(repository?.commits || [], now); + const data = generateCommitData(repository.commits, now); const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); @@ -137,23 +162,11 @@ export function CommitActivityHeatmap({ // Month labels - derived from the rolling 52-week window const months = [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; - // Collect month label positions (keep unique month+year, so labels slide forward over time) - const monthPositions: Array<{ month: string; x: number; fullDate: Date }> = - []; + const monthPositions: Array<{ month: string; x: number; fullDate: Date }> = []; const seenMonthKeys = new Set(); weeks.forEach((weekNum, weekIndex) => { @@ -177,10 +190,8 @@ export function CommitActivityHeatmap({ } }); - // Sort by x-position (left to right) monthPositions.sort((a, b) => a.x - b.x); - // Draw month labels monthPositions.forEach(({ month, x }) => { g.append("text") .attr("class", "month-label") @@ -267,7 +278,6 @@ export function CommitActivityHeatmap({ } }); - // Animate cells on load cell .attr("opacity", 0) .transition() @@ -294,7 +304,6 @@ export function CommitActivityHeatmap({ .ticks(5) .tickFormat((d) => `${d}`); - // Gradient for legend const defs = svg.append("defs"); const gradient = defs .append("linearGradient") @@ -345,7 +354,6 @@ export function CommitActivityHeatmap({ .text("More"); }, [repository, now]); - // Get commits for selected date const getCommitsForDate = (date: string) => { return ( repository?.commits?.filter((commit: any) => { @@ -369,16 +377,9 @@ export function CommitActivityHeatmap({ Contribution activity over the last 52 weeks

- {!repository?.commits || repository.commits.length === 0 ? ( - - ) : ( - <> -
- + 0 || commit.deletions > 0 ? (
{commit.additions > 0 && ( - - +{commit.additions} - + +{commit.additions} )} {commit.deletions > 0 && ( - - -{commit.deletions} - + -{commit.deletions} )}
) : null} @@ -460,28 +457,22 @@ export function CommitActivityHeatmap({
)} - - )} - -
+
); -} +} \ No newline at end of file From 56b00210da3fdbbbc7a81b688f462d8cda9af389 Mon Sep 17 00:00:00 2001 From: TanmayNelge Date: Fri, 22 May 2026 12:29:45 +0530 Subject: [PATCH 083/123] docs: mark worker scripts as legacy/optional in GETTING_STARTED.md (#4) --- GETTING_STARTED.md | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index 02eb2376..f70c1470 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -28,13 +28,13 @@ cp .env.example .env.local Edit `.env.local` and fill in your values: ```env -# Database - Get from https://neon.tech +# Database - Get from [https://neon.tech](https://neon.tech) DATABASE_URL=postgresql://user:password@host/database?sslmode=require&schema=public # JWT Secret - Generate a secure random string JWT_SECRET=your-super-secret-jwt-key-change-this-in-production -# Gemini AI - Get from https://makersuite.google.com/app/apikey +# Gemini AI - Get from [https://makersuite.google.com/app/apikey](https://makersuite.google.com/app/apikey) GEMINI_API_KEY=your_gemini_api_key_here # NextAuth (required for Google login) @@ -47,7 +47,7 @@ GOOGLE_CLIENT_SECRET=your-google-client-secret # Optional: Base URL for API calls # Leave unset to use same-origin `/api/...` (recommended). -# If you set it, avoid a trailing slash (e.g. https://example.com, not https://example.com/) +# If you set it, avoid a trailing slash (e.g. [https://example.com](https://example.com), not [https://example.com/](https://example.com/)) # NEXT_PUBLIC_API_URL=http://localhost:3000 ``` @@ -108,6 +108,8 @@ Open [http://localhost:3000](http://localhost:3000) in your browser. ### Running the App +Note on Background Tasks: For standard local development, background tasks are handled seamlessly by Next.js API routes. The legacy worker script is optional and generally not required. + ```bash npm run dev # Development server npm run build # Production build @@ -115,6 +117,14 @@ npm start # Production server npm run lint # Lint code ``` +### Legacy Worker (Optional) + +If you are testing specific background workflows locally that mimic a custom non-serverless deployment, you can optionally run the worker: + +```bash +npm run worker # Run background tasks (Legacy / Optional) +``` + ### Database Management ```bash @@ -166,12 +176,15 @@ All endpoints are prefixed with `/api`: ### Vercel (Recommended) +Vercel automatically handles background processes via serverless functions, so the worker script is not needed for this deployment method. + 1. Push code to GitHub 2. Import in Vercel dashboard 3. Add environment variables 4. Deploy automatically ### Docker +If self-hosting via Docker, you can run the primary application container. Depending on your scaling needs, you may optionally choose to run the legacy worker as a secondary process. ```bash docker build -t gitverse-nextjs . @@ -179,10 +192,15 @@ docker run -p 3000:3000 gitverse-nextjs ``` ### Manual Deployment +For deployments on custom servers (e.g., EC2, VPS) where serverless functions aren't available, you will need to run the application build alongside the worker script to process background queues ```bash npm run build npm start + +# In a separate terminal or using a process manager like PM2: +npm run worker + ``` ## Troubleshooting From 4ac038aa62f7b5e6ad8f75b673815e66ea5212c5 Mon Sep 17 00:00:00 2001 From: Tanisha-sharma7302 Date: Fri, 22 May 2026 12:49:39 +0530 Subject: [PATCH 084/123] fix: add empty state with CTA for AI UI - issue #181 --- src/components/ai/AIEmptyState.tsx | 36 +++ src/components/ai/AIRepositoryOverlay.tsx | 375 +--------------------- 2 files changed, 37 insertions(+), 374 deletions(-) create mode 100644 src/components/ai/AIEmptyState.tsx diff --git a/src/components/ai/AIEmptyState.tsx b/src/components/ai/AIEmptyState.tsx new file mode 100644 index 00000000..5e118e66 --- /dev/null +++ b/src/components/ai/AIEmptyState.tsx @@ -0,0 +1,36 @@ +import { Sparkles } from "lucide-react"; + +interface AIEmptyStateProps { + onGetStarted?: () => void; + title?: string; + description?: string; +} + +export function AIEmptyState({ + onGetStarted, + title = "No AI suggestions yet", + description = "Stage your changes and let AI generate a smart commit message for you.", +}: AIEmptyStateProps) { + return ( +
+
+ +
+
+

{title}

+

+ {description} +

+
+ {onGetStarted && ( + + )} +
+ ); +} diff --git a/src/components/ai/AIRepositoryOverlay.tsx b/src/components/ai/AIRepositoryOverlay.tsx index a612187c..5f282702 100644 --- a/src/components/ai/AIRepositoryOverlay.tsx +++ b/src/components/ai/AIRepositoryOverlay.tsx @@ -1,374 +1 @@ -import { useState, useRef, useEffect } from "react"; -import { Send, Loader2, X, Minimize2, Maximize2, Sparkles } from "lucide-react"; -import { Card } from "@/components/ui"; -import { geminiService, ChatMessage } from "@/services/gemini"; -import { useToast } from "@/hooks/use-toast"; -import { useAuth } from "@/contexts/AuthContext"; - -interface AIRepositoryOverlayProps { - repository: { - name: string; - description?: string; - languages: { name: string; percentage: number }[]; - stats?: { - commits: number; - contributors: number; - files: number; - branches?: number; - lines?: number; - stars?: number; - forks?: number; - }; - recentCommits?: any[]; - contributors?: any[]; - branches?: any[]; - }; -} - -export function AIRepositoryOverlay({ repository }: AIRepositoryOverlayProps) { - const [isOpen, setIsOpen] = useState(false); - const [isMinimized, setIsMinimized] = useState(false); - const [messages, setMessages] = useState([]); - const [input, setInput] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [streamingMessage, setStreamingMessage] = useState(""); - const [contextSent, setContextSent] = useState(false); - const messagesEndRef = useRef(null); - const { toast } = useToast(); - const { isAuthenticated, isLoading: isAuthLoading } = useAuth(); - - const scrollToBottom = () => { - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }; - - useEffect(() => { - scrollToBottom(); - }, [messages, streamingMessage]); - - useEffect(() => { - if (isOpen && messages.length === 0) { - const greeting: ChatMessage = { - role: "assistant", - content: `Hi! I'm here to help you understand the **${repository.name}** repository. I can answer questions about the code, architecture, commits, contributors, and more. What would you like to know?`, - timestamp: new Date(), - }; - setMessages([greeting]); - } - }, [isOpen, repository.name]); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - if (!input.trim() || isLoading) return; - - if (isAuthLoading || !isAuthenticated) { - toast({ - title: "Login required", - description: "Please log in to use the repository assistant.", - variant: "destructive", - }); - return; - } - - const userMessage: ChatMessage = { - role: "user", - content: input, - timestamp: new Date(), - }; - - setMessages((prev) => [...prev, userMessage]); - setInput(""); - setIsLoading(true); - setStreamingMessage(""); - - try { - // Build comprehensive repository context only for first message - const contributorsArray = Array.isArray(repository.contributors) - ? repository.contributors - : []; - const contributorsCount = - repository.stats?.contributors || contributorsArray.length || 0; - const totalLines = repository.stats?.lines || 0; - - // Debug log to check contributor data - console.log("Contributors data:", { - contributorsArray, - contributorsCount, - hasContributors: contributorsArray.length > 0, - }); - - let contextualPrompt = ""; - - // Only send full context on first message - if (!contextSent) { - const context = { - name: repository.name, - description: repository.description, - languages: repository.languages.map((l) => l.name), - stats: { - commits: repository.stats?.commits || 0, - contributors: contributorsCount, - files: repository.stats?.files || 0, - branches: repository.stats?.branches || 0, - lines: totalLines, - stars: repository.stats?.stars || 0, - forks: repository.stats?.forks || 0, - }, - recentActivity: repository.recentCommits - ?.slice(0, 5) - .map((c: any) => ({ - message: c.message, - author: c.author || c.authorName, - date: c.date || c.committedAt, - })), - topContributors: contributorsArray.slice(0, 5).map((c: any) => ({ - name: c.name, - commits: c.commits, - percentage: c.percentage, - })), - branches: repository.branches?.map((b: any) => b.name), - }; - - contextualPrompt = `You are analyzing the ${context.name} repository. - -===== REPOSITORY DATA ===== -Repository: ${context.name} -Description: ${context.description || "N/A"} -Languages: ${context.languages.join(", ")} -Total Commits: ${context.stats.commits} -Total Contributors: ${context.stats.contributors} -Total Files: ${context.stats.files} -Total Branches: ${context.stats.branches} -Lines of Code: ${context.stats.lines.toLocaleString()} -Stars: ${context.stats.stars} -Forks: ${context.stats.forks} - -${ - context.topContributors && context.topContributors.length > 0 - ? `===== TOP CONTRIBUTORS (BY COMMIT COUNT) ===== -${context.topContributors.map((c: any, i: number) => `${i + 1}. ${c.name} - ${c.commits} commits (${c.percentage?.toFixed(1)}% of total)`).join("\n")} -The contributor with the most commits is: ${context.topContributors[0].name} with ${context.topContributors[0].commits} commits.` - : "===== CONTRIBUTORS =====\nContributor information is not yet available for this repository." -} - -${ - context.recentActivity && context.recentActivity.length > 0 - ? `===== RECENT COMMITS ===== -${context.recentActivity.map((c: any, idx: number) => `${idx + 1}. "${c.message}" by ${c.author}`).join("\n")}` - : "" -} - -${context.branches && context.branches.length > 0 ? `===== BRANCHES =====\n${context.branches.join(", ")}` : ""} - -===== INSTRUCTIONS ===== -You MUST answer questions using ONLY the data provided above. Do NOT: -- Make up names, numbers, or any information not listed above -- Suggest running git commands or checking GitHub -- Say you don't have access to the repository - -When asked about contributors or who made the most commits, use the exact names and numbers from the "TOP CONTRIBUTORS" section above. - -User Question: ${input}`; - setContextSent(true); - } else { - // For follow-up questions, include recent conversation history - const recentMessages = messages.slice(-4); // Last 2 exchanges - const conversationHistory = recentMessages - .map( - (msg) => - `${msg.role === "user" ? "User" : "Assistant"}: ${msg.content}` - ) - .join("\n\n"); - - contextualPrompt = `${conversationHistory}\n\n${input}`; - } - - let fullResponse = ""; - const stream = geminiService.chatStream(contextualPrompt); - - for await (const chunk of stream) { - fullResponse += chunk; - setStreamingMessage(fullResponse); - } - - const assistantMessage: ChatMessage = { - role: "assistant", - content: fullResponse, - timestamp: new Date(), - }; - - setMessages((prev) => [...prev, assistantMessage]); - setStreamingMessage(""); - } catch (error) { - console.error("Chat error:", error); - toast({ - title: "Error", - description: - error instanceof Error ? error.message : "Failed to get AI response", - variant: "destructive", - }); - } finally { - setIsLoading(false); - } - }; - - const formatMessage = (content: string) => { - return content.split("\n").map((line, i) => { - // Bold text - line = line.replace(/\*\*(.*?)\*\*/g, "$1"); - // Inline code - line = line.replace( - /`(.*?)`/g, - '$1' - ); - // Bullet points - if (line.trim().startsWith("- ")) { - line = '•' + line.substring(2); - } - return ( -
- ); - }); - }; - - if (!isOpen) { - return ( - - ); - } - - return ( -
- - {/* Header */} -
-
- -

Repository Assistant

-
-
- - -
-
- - {/* Chat Area */} - {!isMinimized && ( - <> -
- {messages.map((message, index) => ( -
- {message.role === "assistant" && ( -
- -
- )} -
-
- {formatMessage(message.content)} -
-
-
- ))} - - {streamingMessage && ( -
-
- -
-
-
- {formatMessage(streamingMessage)} -
-
-
- )} - - {isLoading && !streamingMessage && ( -
-
- -
-
-
- Thinking... -
-
-
- )} - -
-
- - {/* Input Area */} -
-
- setInput(e.target.value)} - placeholder="Ask about this repository..." - className="flex-1 bg-white/5 border border-white/10 rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary/50 placeholder:text-muted-foreground" - disabled={isLoading} - /> - -
-
- - )} - -
- ); -} + \ No newline at end of file From a97e433b12a5a5fd78ff3b36b42ed3b80f821511 Mon Sep 17 00:00:00 2001 From: smriti-nyx Date: Fri, 22 May 2026 12:50:19 +0530 Subject: [PATCH 085/123] feat: add empty state and primary CTA for auth UI (#119) --- src/pages/Dashboard.tsx | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 7a21a2e4..3d140484 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -290,8 +290,24 @@ export default function Dashboard() { Loading repositories...
) : recentRepositories.length === 0 ? ( -
- No repositories yet. Add your first repository above! +
+
+ +
+
+

No repositories yet

+

+ Analyze your first repository to get started +

+
+ +
) : (
From e4fb619a58d8c9d38bc125c7e20678335fac6d82 Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Fri, 22 May 2026 13:04:41 +0530 Subject: [PATCH 086/123] docs: document GitHub App permissions and environment setup --- README.md | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c7b0137f..be567a7a 100644 --- a/README.md +++ b/README.md @@ -299,14 +299,25 @@ cp .env.example .env.local | `GOOGLE_CLIENT_ID` | Google OAuth 2.0 client ID | [Google Cloud Console](https://console.cloud.google.com) → **APIs & Services → Credentials → Create Credentials → OAuth client ID** (Web application). | | `GOOGLE_CLIENT_SECRET` | Google OAuth 2.0 client secret | Obtained alongside `GOOGLE_CLIENT_ID` in the same step above. | -### GitHub App Variables (for PR reviews) +### GitHub App Configuration (for PR reviews) -| Variable | Description | How to obtain | +GitVerse uses a GitHub App to analyze repositories and post PR reviews. + +**Required Permissions:** +When creating your GitHub App, ensure you grant the following permissions: +- **Repository Permissions**: + - `Contents`: Read-only (Required to fetch repository code for analysis) + - `Metadata`: Read-only (Mandatory for all GitHub Apps) + - `Pull requests`: Read & Write (Required to read PR changes and post review comments) + - `Issues`: Read & Write (Required if tracking or commenting on issues) +- **Subscribe to events**: `Pull request` + +| Variable | Description & Usage | How to obtain | | :--- | :--- | :--- | -| `GITHUB_APP_ID` | Numeric ID of your GitHub App | [GitHub Developer Settings](https://github.com/settings/apps) → create or open your App → copy **App ID**. | -| `GITHUB_APP_PRIVATE_KEY` | RSA private key for the GitHub App | In your GitHub App settings → **Generate a private key** → paste contents with literal `\n` line breaks. | -| `GITHUB_APP_SLUG` | URL slug of your GitHub App | The part after `github.com/apps/` in the App's public URL. | -| `GITHUB_WEBHOOK_SECRET` | Secret to verify webhook payloads | Set any strong random string here and enter the same value in your GitHub App's webhook configuration. | +| `GITHUB_APP_ID` | Numeric ID of your GitHub App. Used to authenticate API requests as the App. | [GitHub Developer Settings](https://github.com/settings/apps) → create or open your App → copy **App ID**. | +| `GITHUB_APP_PRIVATE_KEY` | RSA private key. Used to sign JWTs for GitHub API authentication. | In your GitHub App settings → **Generate a private key** → paste contents with literal `\n` line breaks. | +| `GITHUB_APP_SLUG` | URL slug of your GitHub App. Used to generate installation URLs. | The part after `github.com/apps/` in the App's public URL. | +| `GITHUB_WEBHOOK_SECRET` | Secret string. Used to verify that incoming webhook payloads genuinely came from GitHub. | Set any strong random string here and enter the same value in your GitHub App's webhook configuration. | ### Optional Variables @@ -414,6 +425,17 @@ npm run dev -- -p 3001 2. Run `npm run build` locally first to catch errors before pushing. 3. Check that your Node.js version in Vercel matches the one used locally (see `engines` in `package.json`). +### GitHub App Integration Issues + +**Symptoms:** PR reviews aren't posting, repo analysis fails, or webhook errors. + +**Fix:** +- **Missing Credentials:** Ensure `GITHUB_APP_ID`, `GITHUB_APP_PRIVATE_KEY`, and other related variables are correctly populated in your `.env.local` or Vercel environment settings. Without these, PR reviews and repository analysis will fail. Build or deployment failures caused by missing secrets are usually flagged during the `npm run build` step. +- **Permission Denied:** Verify your GitHub App has the exact permissions listed in the [GitHub App Configuration](#github-app-configuration-for-pr-reviews) section (especially Read/Write on Pull Requests and Read on Contents). If you updated permissions on an existing App, you must accept the new permissions on your App installations. +- **Invalid Callback URL / Webhook:** Ensure the webhook URL in the GitHub App settings exactly matches your deployed domain's endpoint (e.g., `https:///api/integrations/github/webhook`). +- **Vercel Environment Setup:** In Vercel, format the `GITHUB_APP_PRIVATE_KEY` correctly. Sometimes newlines get mangled during copy-pasting. Enclosing the key in double quotes in the Vercel dashboard or ensuring literal `\n` characters are used can prevent parsing errors. +- **Error Handling (Local Dev):** If running locally, check your terminal for missing environment variable warnings at startup. If analysis fails silently, review the terminal logs for explicit GitHub API permission or authentication errors. + ## 🤠Contributing 1. Fork the repository From 1bfc5c35e53b3ee9269ce9e74121dd8d28efeef0 Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Fri, 22 May 2026 13:08:17 +0530 Subject: [PATCH 087/123] security: redact sensitive logs and strengthen authorization checks --- app/api/auth/login/route.ts | 2 +- app/api/auth/me/route.ts | 2 +- app/api/repositories/route.ts | 6 ++---- app/api/users/me/route.ts | 4 ++-- lib/middleware.ts | 30 ++++++++++++++++++------------ 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts index 3f9249b0..c36f9165 100644 --- a/app/api/auth/login/route.ts +++ b/app/api/auth/login/route.ts @@ -75,7 +75,7 @@ export async function POST(request: NextRequest) { token, }); } catch (error) { - console.error("Login error:", error); + console.error("Login error:", error instanceof Error ? error.message : "Unknown error"); return NextResponse.json( { error: "Internal server error" }, { status: 500 } diff --git a/app/api/auth/me/route.ts b/app/api/auth/me/route.ts index 7cc33d7b..59139723 100644 --- a/app/api/auth/me/route.ts +++ b/app/api/auth/me/route.ts @@ -31,7 +31,7 @@ export async function GET(request: NextRequest) { }, }); } catch (error) { - console.error("Get user error:", error); + console.error("Get user error:", error instanceof Error ? error.message : "Unknown error"); return NextResponse.json( { error: "Internal server error" }, { status: 500 } diff --git a/app/api/repositories/route.ts b/app/api/repositories/route.ts index 390d8daf..890fd59c 100644 --- a/app/api/repositories/route.ts +++ b/app/api/repositories/route.ts @@ -11,7 +11,6 @@ export async function POST(request: NextRequest) { console.log("Create repository request:", { name, - url, userId: user.userId, }); @@ -50,8 +49,7 @@ export async function POST(request: NextRequest) { { status: 201 } ); } catch (error: any) { - console.error("Create repository error:", error); - console.error("Error stack:", error.stack); + console.error("Create repository error:", error instanceof Error ? error.message : "Unknown error"); if (isHttpError(error)) { return NextResponse.json( { error: error.message }, @@ -72,7 +70,7 @@ export async function GET(request: NextRequest) { return NextResponse.json({ repositories }); } catch (error: any) { - console.error("List repositories error:", error); + console.error("List repositories error:", error instanceof Error ? error.message : "Unknown error"); if (isHttpError(error)) { return NextResponse.json( { error: error.message }, diff --git a/app/api/users/me/route.ts b/app/api/users/me/route.ts index 02d01429..4047495b 100644 --- a/app/api/users/me/route.ts +++ b/app/api/users/me/route.ts @@ -39,7 +39,7 @@ export async function GET(request: NextRequest) { isGoogleLinked: hasGoogleAccount, }); } catch (error: any) { - console.error("Error fetching user:", error); + console.error("Error fetching user:", error instanceof Error ? error.message : "Unknown error"); return NextResponse.json( { message: "Failed to fetch user" }, { status: 500 } @@ -57,7 +57,7 @@ export async function DELETE(request: NextRequest) { return NextResponse.json({ message: "Account deleted" }); } catch (error: any) { - console.error("Error deleting account:", error); + console.error("Error deleting account:", error instanceof Error ? error.message : "Unknown error"); if (error?.code === "P2025") { return NextResponse.json({ message: "User not found" }, { status: 404 }); } diff --git a/lib/middleware.ts b/lib/middleware.ts index f1c0d5bc..789a17d7 100644 --- a/lib/middleware.ts +++ b/lib/middleware.ts @@ -9,28 +9,34 @@ export interface AuthenticatedRequest { export async function getAuthUser( request: NextRequest ): Promise { - const authHeader = request.headers.get("authorization"); + try { + const authHeader = request.headers.get("authorization"); - // 1) Existing JWT auth (Authorization: Bearer ...) - if (authHeader && authHeader.startsWith("Bearer ")) { - const token = authHeader.substring(7); - const payload = verifyToken(token); - if (payload) return payload; - } + // 1) Existing JWT auth (Authorization: Bearer ...) + if (authHeader && authHeader.startsWith("Bearer ")) { + const token = authHeader.substring(7).trim(); + if (token) { + const payload = verifyToken(token); + if (payload && typeof payload.userId === "number" && payload.userId > 0) { + return payload; + } + } + } - // 2) NextAuth session cookie (Google OAuth) - try { + // 2) NextAuth session cookie (Google OAuth) const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET, }); + if (!token?.sub || !token.email) return null; const userId = Number(token.sub); - if (!Number.isFinite(userId)) return null; + if (!Number.isFinite(userId) || userId <= 0) return null; return { userId, email: token.email }; - } catch { + } catch (error) { + // Safely return null on any error without logging sensitive information return null; } } @@ -38,7 +44,7 @@ export async function getAuthUser( export async function requireAuth(request: NextRequest): Promise { const user = await getAuthUser(request); - if (!user) { + if (!user || !user.userId) { throw new HttpError(401, "Unauthorized"); } From 952472006d41e3eda23f53a1a55f913dd0a9b15a Mon Sep 17 00:00:00 2001 From: smriti-nyx Date: Fri, 22 May 2026 13:12:01 +0530 Subject: [PATCH 088/123] fix: add email format validation to auth routes (#98) --- app/api/auth/login/route.ts | 8 ++++++++ app/api/auth/signup/route.ts | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts index 3f9249b0..124198f7 100644 --- a/app/api/auth/login/route.ts +++ b/app/api/auth/login/route.ts @@ -15,6 +15,14 @@ export async function POST(request: NextRequest) { { status: 400 } ); } + // Email format validation + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + return NextResponse.json( + { error: "Please provide a valid email address" }, + { status: 400 } + ); + } // Find user const user = await prisma.user.findUnique({ diff --git a/app/api/auth/signup/route.ts b/app/api/auth/signup/route.ts index 7287dc64..39a58d0f 100644 --- a/app/api/auth/signup/route.ts +++ b/app/api/auth/signup/route.ts @@ -22,6 +22,14 @@ export async function POST(request: NextRequest) { { status: 400 } ); } + // Email format validation + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + return NextResponse.json( + { error: "Please provide a valid email address" }, + { status: 400 } + ); + } // Check if user already exists const existingUser = await prisma.user.findUnique({ From f518c5988a0f76c4f95f9008b680416124b509a4 Mon Sep 17 00:00:00 2001 From: Vasudha Agrawal Date: Fri, 22 May 2026 13:14:07 +0530 Subject: [PATCH 089/123] Add adaptive dark mode with persistent theme toggle --- src/components/layout/Navbar.tsx | 3 +++ src/components/layout/ThemeToggle.tsx | 23 +++++++++++++++++++++++ src/context/ThemeContext.tsx | 8 ++++---- 3 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 src/components/layout/ThemeToggle.tsx diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx index e601dca5..03ea96f9 100644 --- a/src/components/layout/Navbar.tsx +++ b/src/components/layout/Navbar.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react' import Link from 'next/link' import { GitBranch, Menu, X } from 'lucide-react' import { Button } from '@/components/ui' +import { ThemeToggle } from './ThemeToggle' export const Navbar: React.FC = () => { const [isOpen, setIsOpen] = useState(false) @@ -42,6 +43,7 @@ export const Navbar: React.FC = () => { {/* CTA Buttons */}
+ @@ -75,6 +77,7 @@ export const Navbar: React.FC = () => { ))}
+ diff --git a/src/components/layout/ThemeToggle.tsx b/src/components/layout/ThemeToggle.tsx new file mode 100644 index 00000000..90c7a5a2 --- /dev/null +++ b/src/components/layout/ThemeToggle.tsx @@ -0,0 +1,23 @@ +'use client' + +import { Moon, Sun } from 'lucide-react' +import { useTheme } from '@/context/ThemeContext' + +export const ThemeToggle: React.FC = () => { + const { theme, toggleTheme } = useTheme() + + return ( + + ) +} diff --git a/src/context/ThemeContext.tsx b/src/context/ThemeContext.tsx index b03f1d28..53ae6bf7 100644 --- a/src/context/ThemeContext.tsx +++ b/src/context/ThemeContext.tsx @@ -20,6 +20,10 @@ export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ childre const saved = localStorage.getItem('theme') if (saved) { setTheme(saved as Theme) + } else { + // Respect system preference on first visit + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches + setTheme(prefersDark ? 'dark' : 'light') } }, []) @@ -37,10 +41,6 @@ export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ childre setTheme((prev) => (prev === 'light' ? 'dark' : 'light')) } - if (!mounted) { - return null - } - return {children} } From 071fda1c541d0f9aad90cd3887c8516eb01d5f47 Mon Sep 17 00:00:00 2001 From: TanmayNelge Date: Fri, 22 May 2026 13:17:44 +0530 Subject: [PATCH 090/123] feat(auth): implement in-memory rate limiting for login and signup --- .env.example | 5 +++++ app/api/auth/login/route.ts | 21 +++++++++++++++++++-- app/api/auth/signup/route.ts | 18 ++++++++++++++++++ src/utils/rateLimit.ts | 27 +++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 src/utils/rateLimit.ts diff --git a/.env.example b/.env.example index 167b8c15..a18c0aea 100644 --- a/.env.example +++ b/.env.example @@ -35,3 +35,8 @@ GITHUB_APP_STATE_SECRET= # Optional: For client-side API calls NEXT_PUBLIC_API_URL=http://localhost:3000 + + +# Rate Limiting (In-Memory) +RATE_LIMIT_WINDOW_MS=900000 # 15 minutes in milliseconds +RATE_LIMIT_MAX_REQUESTS=5 # Max attempts per window \ No newline at end of file diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts index 3f9249b0..d611d3ac 100644 --- a/app/api/auth/login/route.ts +++ b/app/api/auth/login/route.ts @@ -1,10 +1,28 @@ import { NextRequest, NextResponse } from "next/server"; +import { checkRateLimit } from '@/src/utils/rateLimit'; import bcrypt from "bcryptjs"; import prisma from "@/lib/prisma"; import { generateToken } from "@/lib/auth"; export async function POST(request: NextRequest) { try { + // 1. EXTRACT IP AND CHECK RATE LIMIT FIRST + const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown-ip'; + const rateLimitResult = checkRateLimit(ip); + + if (!rateLimitResult.success) { + return NextResponse.json( + { error: "Too many requests. Please try again later." }, + { + status: 429, + headers: { + 'Retry-After': rateLimitResult.retryAfter?.toString() || '900' + } + } + ); + } + + // 2. PARSE BODY const body = await request.json(); const { email, password } = body; @@ -29,7 +47,6 @@ export async function POST(request: NextRequest) { } // Security: never allow password login for Google-only accounts. - // A "Google-only" account is a user without a local password, but with a linked Google OAuth account. if (!user.passwordHash) { const hasGoogleAccount = (await prisma.account.count({ @@ -81,4 +98,4 @@ export async function POST(request: NextRequest) { { status: 500 } ); } -} +} \ No newline at end of file diff --git a/app/api/auth/signup/route.ts b/app/api/auth/signup/route.ts index 7287dc64..420fb9f1 100644 --- a/app/api/auth/signup/route.ts +++ b/app/api/auth/signup/route.ts @@ -1,10 +1,28 @@ import { NextRequest, NextResponse } from "next/server"; +import { checkRateLimit } from '@/src/utils/rateLimit'; import bcrypt from "bcryptjs"; import prisma from "@/lib/prisma"; import { generateToken } from "@/lib/auth"; export async function POST(request: NextRequest) { try { + // 1. EXTRACT IP AND CHECK RATE LIMIT FIRST + const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown-ip'; + const rateLimitResult = checkRateLimit(ip); + + if (!rateLimitResult.success) { + return NextResponse.json( + { error: "Too many requests. Please try again later." }, + { + status: 429, + headers: { + 'Retry-After': rateLimitResult.retryAfter?.toString() || '900' + } + } + ); + } + + //2. PARSE BODY const body = await request.json(); const { email, password, name } = body; diff --git a/src/utils/rateLimit.ts b/src/utils/rateLimit.ts new file mode 100644 index 00000000..d95bda90 --- /dev/null +++ b/src/utils/rateLimit.ts @@ -0,0 +1,27 @@ +// This Map stores the IP address as the key, and the attempt count + expiration time as the value. +const rateLimitMap = new Map(); + +export function checkRateLimit(ip: string) { + const windowMs = parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000', 10); + const maxRequests = parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '5', 10); + const now = Date.now(); + + const record = rateLimitMap.get(ip); + + // If no record exists, or the previous penalty window has expired, reset them. + if (!record || record.expiresAt < now) { + rateLimitMap.set(ip, { count: 1, expiresAt: now + windowMs }); + return { success: true }; + } + + // If they hit the limit, calculate how many seconds they need to wait (retry hint) + if (record.count >= maxRequests) { + const retryAfterSeconds = Math.ceil((record.expiresAt - now) / 1000); + return { success: false, retryAfter: retryAfterSeconds }; + } + + // Otherwise, increment their attempt count + record.count += 1; + rateLimitMap.set(ip, record); + return { success: true }; +} \ No newline at end of file From 004d0abb09ef4557242c20096e8a77c8d51854ef Mon Sep 17 00:00:00 2001 From: TanmayNelge Date: Fri, 22 May 2026 13:23:32 +0530 Subject: [PATCH 091/123] docs: align worker requirement wording across deployments per review Clarify instructions for running Docker and manual deployments. --- GETTING_STARTED.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index f70c1470..29fbd3ad 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -184,7 +184,7 @@ Vercel automatically handles background processes via serverless functions, so t 4. Deploy automatically ### Docker -If self-hosting via Docker, you can run the primary application container. Depending on your scaling needs, you may optionally choose to run the legacy worker as a secondary process. +If self-hosting via Docker, run the primary application container. If your deployment uses queue-based background processing, run the legacy worker as a separate process/container. ```bash docker build -t gitverse-nextjs . @@ -192,7 +192,7 @@ docker run -p 3000:3000 gitverse-nextjs ``` ### Manual Deployment -For deployments on custom servers (e.g., EC2, VPS) where serverless functions aren't available, you will need to run the application build alongside the worker script to process background queues +For deployments on custom servers (e.g., EC2, VPS) where serverless functions aren't available, run the app build. If queue-based background processing is enabled, also run the worker script (for example via PM2/systemd). ```bash npm run build From fece1de7312a6ac2ccab6c1b30f1243431824461 Mon Sep 17 00:00:00 2001 From: TanmayNelge Date: Fri, 22 May 2026 13:32:57 +0530 Subject: [PATCH 092/123] fix: address CodeRabbit review comments for rate limiter --- app/api/auth/login/route.ts | 8 +++++--- app/api/auth/signup/route.ts | 6 ++++-- src/utils/rateLimit.ts | 31 +++++++++++++++++++++++++------ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts index d611d3ac..b0fb7a03 100644 --- a/app/api/auth/login/route.ts +++ b/app/api/auth/login/route.ts @@ -6,10 +6,12 @@ import { generateToken } from "@/lib/auth"; export async function POST(request: NextRequest) { try { - // 1. EXTRACT IP AND CHECK RATE LIMIT FIRST - const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown-ip'; +// 1. EXTRACT IP AND CHECK RATE LIMIT FIRST + const forwardedFor = request.headers.get('x-forwarded-for'); + const ip = forwardedFor?.split(',')[0]?.trim() || request.headers.get('x-real-ip') || 'unknown-ip'; + const rateLimitResult = checkRateLimit(ip); - + if (!rateLimitResult.success) { return NextResponse.json( { error: "Too many requests. Please try again later." }, diff --git a/app/api/auth/signup/route.ts b/app/api/auth/signup/route.ts index 420fb9f1..6f4cbe51 100644 --- a/app/api/auth/signup/route.ts +++ b/app/api/auth/signup/route.ts @@ -7,8 +7,10 @@ import { generateToken } from "@/lib/auth"; export async function POST(request: NextRequest) { try { // 1. EXTRACT IP AND CHECK RATE LIMIT FIRST - const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown-ip'; - const rateLimitResult = checkRateLimit(ip); + const forwardedFor = request.headers.get('x-forwarded-for'); + const ip = forwardedFor?.split(',')[0]?.trim() || request.headers.get('x-real-ip') || 'unknown-ip'; + + const rateLimitResult = checkRateLimit(ip); if (!rateLimitResult.success) { return NextResponse.json( diff --git a/src/utils/rateLimit.ts b/src/utils/rateLimit.ts index d95bda90..6bf957f7 100644 --- a/src/utils/rateLimit.ts +++ b/src/utils/rateLimit.ts @@ -1,26 +1,45 @@ -// This Map stores the IP address as the key, and the attempt count + expiration time as the value. const rateLimitMap = new Map(); +let lastCleanupAt = 0; + +const DEFAULT_WINDOW_MS = 900_000; +const DEFAULT_MAX_REQUESTS = 5; export function checkRateLimit(ip: string) { - const windowMs = parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000', 10); - const maxRequests = parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '5', 10); + // 1. Safely parse environment variables with fallbacks + const parsedWindowMs = Number.parseInt(process.env.RATE_LIMIT_WINDOW_MS ?? '', 10); + const parsedMaxRequests = Number.parseInt(process.env.RATE_LIMIT_MAX_REQUESTS ?? '', 10); + + const windowMs = Number.isInteger(parsedWindowMs) && parsedWindowMs > 0 + ? parsedWindowMs + : DEFAULT_WINDOW_MS; + + const maxRequests = Number.isInteger(parsedMaxRequests) && parsedMaxRequests > 0 + ? parsedMaxRequests + : DEFAULT_MAX_REQUESTS; + const now = Date.now(); + // 2. Periodically prune expired entries to bound memory growth + if (now - lastCleanupAt >= windowMs) { + for (const [key, value] of rateLimitMap.entries()) { + if (value.expiresAt <= now) rateLimitMap.delete(key); + } + lastCleanupAt = now; + } + const record = rateLimitMap.get(ip); - // If no record exists, or the previous penalty window has expired, reset them. + // 3. Apply rate limit logic if (!record || record.expiresAt < now) { rateLimitMap.set(ip, { count: 1, expiresAt: now + windowMs }); return { success: true }; } - // If they hit the limit, calculate how many seconds they need to wait (retry hint) if (record.count >= maxRequests) { const retryAfterSeconds = Math.ceil((record.expiresAt - now) / 1000); return { success: false, retryAfter: retryAfterSeconds }; } - // Otherwise, increment their attempt count record.count += 1; rateLimitMap.set(ip, record); return { success: true }; From 89bc1e58fc21da33049c6c79d56d528a52b61628 Mon Sep 17 00:00:00 2001 From: Vasudha Agrawal Date: Fri, 22 May 2026 13:33:08 +0530 Subject: [PATCH 093/123] Fix CodeRabbit review comments --- src/components/layout/Navbar.tsx | 2 +- src/components/layout/ThemeToggle.tsx | 5 +++-- src/context/ThemeContext.tsx | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx index 03ea96f9..1b3e04dc 100644 --- a/src/components/layout/Navbar.tsx +++ b/src/components/layout/Navbar.tsx @@ -1,3 +1,4 @@ +"use client"; import React, { useState } from 'react' import Link from 'next/link' import { GitBranch, Menu, X } from 'lucide-react' @@ -77,7 +78,6 @@ export const Navbar: React.FC = () => { ))}
- diff --git a/src/components/layout/ThemeToggle.tsx b/src/components/layout/ThemeToggle.tsx index 90c7a5a2..d3fb1559 100644 --- a/src/components/layout/ThemeToggle.tsx +++ b/src/components/layout/ThemeToggle.tsx @@ -7,8 +7,9 @@ export const ThemeToggle: React.FC = () => { const { theme, toggleTheme } = useTheme() return ( -
{onGetStarted && (
- {isAnalyzing ? ( -
+ {/* ── Analyzing spinner (with timeout awareness) ── */} + {isAnalyzing && !analysisTimedOut ? ( +
-
+
-

- Analyzing Repository -

+

Analyzing Repository

- We're analyzing the repository structure, commits, - contributors, and more. + We're analyzing structure, commits, contributors, and more.

-

- {job?.progressPercent != null || job?.progressMessage - ? `${job?.progressPercent ?? 0}% — ${job?.progressMessage || "Working"}` - : "This may take a few moments depending on the repository size..."} + + {/* Progress bar */} +

+
+ {progressMessage} + {progressPercent}% +
+
+
+
+
+ + {/* Elapsed time */} +

+ + {elapsedSeconds > 0 + ? `Running for ${formatElapsed(elapsedSeconds)}` + : "Starting up..."}

+ + {/* Warn if queued too long (>60s with no progress) */} + {elapsedSeconds > 60 && progressPercent === 0 && ( +
+

+ + Still queued after {formatElapsed(elapsedSeconds)}. + The worker runs every 5 minutes via GitHub Actions — + it should pick this up shortly. +

+
+ )}
+
@@ -304,9 +378,50 @@ export default function RepositoryAnalysis() {
+ + ) : analysisTimedOut || analysisError ? ( + /* ── Timeout / error state ── */ +
+
+
+ +
+
+
+

+ {analysisTimedOut ? "Analysis Timed Out" : "Analysis Failed"} +

+

+ {analysisError} +

+
+
+ + +
+
+ ) : ( + /* ── Done — show tabs ── */ <> - {/* Tab navigation */}
{tabs.map((tab) => ( @@ -328,8 +443,6 @@ export default function RepositoryAnalysis() { ))}
- - {/* Content */}
{renderContent()}
)} @@ -351,19 +464,15 @@ export default function RepositoryAnalysis() {
-

- Delete Repository -

+

Delete Repository

Are you sure you want to delete{" "} {repository?.name}? This action cannot be undone and will permanently remove all - repository data, including commits, contributors, and - analysis results. + repository data, including commits, contributors, and analysis results.

-
From c5d6a9238974f4378da8332de0e9d10e00065e9d Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Fri, 22 May 2026 14:58:03 +0530 Subject: [PATCH 099/123] fix: improve GitHub integration handling for renamed repositories --- app/api/integrations/github/import/route.ts | 7 + lib/services/githubService.ts | 155 ++++++++++++++------ lib/services/prReviewService.ts | 4 + 3 files changed, 121 insertions(+), 45 deletions(-) diff --git a/app/api/integrations/github/import/route.ts b/app/api/integrations/github/import/route.ts index 792ddfcc..0f96724f 100644 --- a/app/api/integrations/github/import/route.ts +++ b/app/api/integrations/github/import/route.ts @@ -34,6 +34,13 @@ export async function POST(request: NextRequest) { const github = new GitHubService(token); const repoData = await github.getRepository(parsed.owner, parsed.repo); + if (!repoData) { + return NextResponse.json( + { error: "Repository not found. It may have been renamed, deleted, or is inaccessible with the provided token." }, + { status: 404 } + ); + } + const repository = await repositoryService.createRepository({ name: repoData.name, url: repoData.clone_url, diff --git a/lib/services/githubService.ts b/lib/services/githubService.ts index 18787da7..d85ebd17 100644 --- a/lib/services/githubService.ts +++ b/lib/services/githubService.ts @@ -182,7 +182,7 @@ export class GitHubService { } } - const retryStatusCodes = [502, 503, 504]; + const retryStatusCodes = [409, 502, 503, 504]; if ( (status && retryStatusCodes.includes(status)) || error.code === "ECONNABORTED" || @@ -192,6 +192,7 @@ export class GitHubService { if (config.retryCount < 3) { config.retryCount += 1; const backoff = Math.pow(2, config.retryCount) * 1000 + Math.random() * 1000; + console.log(`Retrying GitHub API request ${config.url} (attempt ${config.retryCount}) due to ${status || error.code}...`); await new Promise((resolve) => setTimeout(resolve, backoff)); return this.client(config); } @@ -217,9 +218,25 @@ export class GitHubService { /** * Get repository information */ - async getRepository(owner: string, repo: string): Promise { - const response = await this.client.get(`/repos/${owner}/${repo}`); - return response.data; + async getRepository(owner: string, repo: string): Promise { + try { + const response = await this.client.get(`/repos/${owner}/${repo}`); + const data = response.data as GitHubRepository; + + // Detect renamed repositories safely + const expectedFullName = `${owner}/${repo}`.toLowerCase(); + if (data.full_name && data.full_name.toLowerCase() !== expectedFullName) { + console.warn(`GitHub repository renamed: requested ${expectedFullName}, but received ${data.full_name}`); + } + + return data; + } catch (error) { + if (isAxiosError(error) && error.response?.status === 404) { + console.warn(`GitHub repository not found (404): ${owner}/${repo}. It may have been deleted or access was lost.`); + return null; + } + throw sanitizeGitHubError(error); + } } /** @@ -277,8 +294,15 @@ export class GitHubService { * Get repository branches */ async getBranches(owner: string, repo: string): Promise { - const response = await this.client.get(`/repos/${owner}/${repo}/branches`); - return response.data; + try { + const response = await this.client.get(`/repos/${owner}/${repo}/branches`); + return response.data; + } catch (error) { + if (isAxiosError(error) && error.response?.status === 404) { + return []; + } + throw sanitizeGitHubError(error); + } } /** @@ -294,16 +318,22 @@ export class GitHubService { page?: number; }, ): Promise { - const response = await this.client.get(`/repos/${owner}/${repo}/commits`, { - params: { - sha: params?.sha, - path: params?.path, - per_page: params?.per_page || 100, - page: params?.page || 1, - }, - }); - - return response.data; + try { + const response = await this.client.get(`/repos/${owner}/${repo}/commits`, { + params: { + sha: params?.sha, + path: params?.path, + per_page: params?.per_page || 100, + page: params?.page || 1, + }, + }); + return response.data; + } catch (error) { + if (isAxiosError(error) && (error.response?.status === 404 || error.response?.status === 409)) { + return []; // 409 Conflict means repository is empty + } + throw sanitizeGitHubError(error); + } } /** @@ -313,11 +343,18 @@ export class GitHubService { owner: string, repo: string, sha: string, - ): Promise { - const response = await this.client.get( - `/repos/${owner}/${repo}/commits/${sha}`, - ); - return response.data; + ): Promise { + try { + const response = await this.client.get( + `/repos/${owner}/${repo}/commits/${sha}`, + ); + return response.data; + } catch (error) { + if (isAxiosError(error) && error.response?.status === 404) { + return null; + } + throw sanitizeGitHubError(error); + } } /** @@ -327,11 +364,18 @@ export class GitHubService { owner: string, repo: string, pullNumber: number, - ): Promise { - const response = await this.client.get( - `/repos/${owner}/${repo}/pulls/${pullNumber}`, - ); - return response.data; + ): Promise { + try { + const response = await this.client.get( + `/repos/${owner}/${repo}/pulls/${pullNumber}`, + ); + return response.data; + } catch (error) { + if (isAxiosError(error) && error.response?.status === 404) { + return null; + } + throw sanitizeGitHubError(error); + } } /** @@ -347,21 +391,28 @@ export class GitHubService { const maxPages = Math.min(Math.max(params?.max_pages ?? 10, 1), 50); const all: GitHubPullRequestFile[] = []; - for (let page = 1; page <= maxPages; page++) { - const response = await this.client.get( - `/repos/${owner}/${repo}/pulls/${pullNumber}/files`, - { - params: { - per_page: perPage, - page, + try { + for (let page = 1; page <= maxPages; page++) { + const response = await this.client.get( + `/repos/${owner}/${repo}/pulls/${pullNumber}/files`, + { + params: { + per_page: perPage, + page, + }, }, - }, - ); + ); - const items: GitHubPullRequestFile[] = response.data; - if (!Array.isArray(items) || items.length === 0) break; - all.push(...items); - if (items.length < perPage) break; + const items: GitHubPullRequestFile[] = response.data; + if (!Array.isArray(items) || items.length === 0) break; + all.push(...items); + if (items.length < perPage) break; + } + } catch (error) { + if (isAxiosError(error) && error.response?.status === 404) { + return all; // Return what we have (likely empty) if 404 occurs + } + throw sanitizeGitHubError(error); } return all; @@ -429,8 +480,15 @@ export class GitHubService { owner: string, repo: string, ): Promise> { - const response = await this.client.get(`/repos/${owner}/${repo}/languages`); - return response.data; + try { + const response = await this.client.get(`/repos/${owner}/${repo}/languages`); + return response.data; + } catch (error) { + if (isAxiosError(error) && error.response?.status === 404) { + return {}; + } + throw sanitizeGitHubError(error); + } } /** @@ -446,10 +504,17 @@ export class GitHubService { avatar_url: string; }> > { - const response = await this.client.get( - `/repos/${owner}/${repo}/contributors`, - ); - return response.data; + try { + const response = await this.client.get( + `/repos/${owner}/${repo}/contributors`, + ); + return response.data; + } catch (error) { + if (isAxiosError(error) && error.response?.status === 404) { + return []; + } + throw sanitizeGitHubError(error); + } } /** diff --git a/lib/services/prReviewService.ts b/lib/services/prReviewService.ts index 43bdf1a4..ce08668d 100644 --- a/lib/services/prReviewService.ts +++ b/lib/services/prReviewService.ts @@ -165,6 +165,10 @@ export async function reviewPullRequest(params: { params.repo, params.number, ); + if (!pr) { + throw new Error(`Pull request ${params.owner}/${params.repo}#${params.number} not found. It may have been deleted or access is denied.`); + } + const prFiles = await github.getPullRequestFiles( params.owner, params.repo, From 05de6ff63b301197a94b6871e97473ef4108238a Mon Sep 17 00:00:00 2001 From: m4ninja Date: Fri, 22 May 2026 15:13:45 +0530 Subject: [PATCH 100/123] fix: address CodeRabbit review comments - Add lastProgressAt ref to track last time progress actually changed - Use lastProgressAt for timeout check so active jobs are never falsely timed out - Reset lastProgressAt on each progress update in fetchJob - Add analysisTimedOut to polling useEffect dependency array - Reset lastProgressAt in Check Again handler so polling restarts cleanly --- src/pages/RepositoryAnalysis.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/pages/RepositoryAnalysis.tsx b/src/pages/RepositoryAnalysis.tsx index 3d256d8f..a72c05e2 100644 --- a/src/pages/RepositoryAnalysis.tsx +++ b/src/pages/RepositoryAnalysis.tsx @@ -83,6 +83,8 @@ export default function RepositoryAnalysis() { const [elapsedSeconds, setElapsedSeconds] = useState(0); const pollingStartedAt = useRef(null); + // Tracks last time progress changed — prevents falsely timing out active jobs + const lastProgressAt = useRef(null); const elapsedTimer = useRef(null); // ── Elapsed seconds ticker ──────────────────────────────────────── @@ -130,6 +132,9 @@ export default function RepositoryAnalysis() { if (!pollingStartedAt.current) { pollingStartedAt.current = Date.now(); } + if (!lastProgressAt.current) { + lastProgressAt.current = Date.now(); + } let stopped = false; let intervalMs = POLL_INTERVAL_INITIAL_MS; @@ -139,8 +144,8 @@ export default function RepositoryAnalysis() { // ── Timeout guard ────────────────────────────────────────── if ( - pollingStartedAt.current && - Date.now() - pollingStartedAt.current > ANALYSIS_TIMEOUT_MS + lastProgressAt.current && + Date.now() - lastProgressAt.current > ANALYSIS_TIMEOUT_MS ) { stopped = true; setAnalysisTimedOut(true); @@ -165,7 +170,8 @@ export default function RepositoryAnalysis() { return () => { stopped = true; }; - }, [repository?.status, repository?.latestJob?.id, job?.id, job?.status]); + // analysisTimedOut included so Check Again restarts polling + }, [repository?.status, repository?.latestJob?.id, job?.id, job?.status, analysisTimedOut]); // ── Data fetchers ───────────────────────────────────────────────── const fetchRepository = async () => { @@ -197,6 +203,10 @@ export default function RepositoryAnalysis() { ); const nextJob = response.data.job || response.data; + // Reset lastProgressAt whenever progress changes + if (nextJob?.progressPercent !== undefined || nextJob?.progressMessage !== undefined) { + lastProgressAt.current = Date.now(); + } setJob(nextJob); if (nextJob?.status === "DONE") { @@ -401,6 +411,7 @@ export default function RepositoryAnalysis() { setAnalysisTimedOut(false); setAnalysisError(null); pollingStartedAt.current = null; + lastProgressAt.current = null; setElapsedSeconds(0); fetchRepository(); }} From 6841d9f7d587948f99aaf93b81739ee1473829eb Mon Sep 17 00:00:00 2001 From: SuhaniSnehasini Date: Fri, 22 May 2026 15:16:36 +0530 Subject: [PATCH 101/123] fix:address review changes --- app/api/repositories/route.ts | 36 ++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/app/api/repositories/route.ts b/app/api/repositories/route.ts index 8dd592c9..d90a3525 100644 --- a/app/api/repositories/route.ts +++ b/app/api/repositories/route.ts @@ -39,14 +39,22 @@ export async function POST(request: NextRequest) { ); } - if (typeof name !== "string" || !name.trim()) { + if (typeof name !== "string") { return NextResponse.json( { error: "Repository name must be a non-empty string" }, { status: 400 } ); } - if (name.length > 100) { + const trimmedName = name.trim(); + if (!trimmedName) { + return NextResponse.json( + { error: "Repository name must be a non-empty string" }, + { status: 400 } + ); + } + + if (trimmedName.length > 100) { return NextResponse.json( { error: "Repository name must be 100 characters or less" }, { status: 400 } @@ -60,14 +68,22 @@ export async function POST(request: NextRequest) { ); } - if (typeof url !== "string" || !url.trim()) { + if (typeof url !== "string") { + return NextResponse.json( + { error: "Repository URL must be a non-empty string" }, + { status: 400 } + ); + } + + const trimmedUrl = url.trim(); + if (!trimmedUrl) { return NextResponse.json( { error: "Repository URL must be a non-empty string" }, { status: 400 } ); } - if (url.length > 2000) { + if (trimmedUrl.length > 2000) { return NextResponse.json( { error: "Repository URL must be 2000 characters or less" }, { status: 400 } @@ -75,7 +91,7 @@ export async function POST(request: NextRequest) { } try { - const parsedUrl = new URL(url); + const parsedUrl = new URL(trimmedUrl); if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") { return NextResponse.json( { error: "Repository URL must use HTTP or HTTPS protocol" }, @@ -89,6 +105,7 @@ export async function POST(request: NextRequest) { ); } + let trimmedDescription: string | undefined = undefined; if (description !== undefined && description !== null) { if (typeof description !== "string") { return NextResponse.json( @@ -96,7 +113,8 @@ export async function POST(request: NextRequest) { { status: 400 } ); } - if (description.length > 1000) { + trimmedDescription = description.trim(); + if (trimmedDescription.length > 1000) { return NextResponse.json( { error: "Repository description must be 1000 characters or less" }, { status: 400 } @@ -105,9 +123,9 @@ export async function POST(request: NextRequest) { } const repository = await repositoryService.createRepository({ - name: name.trim(), - url: url.trim(), - description: description?.trim() || undefined, + name: trimmedName, + url: trimmedUrl, + description: trimmedDescription || undefined, userId: user.userId, }); From 90903c33c399426458796fb9a5494f639f37eb96 Mon Sep 17 00:00:00 2001 From: m4ninja Date: Fri, 22 May 2026 15:20:14 +0530 Subject: [PATCH 102/123] fix: only reset lastProgressAt on actual progress value change Compare new progressPercent and progressMessage against previous job state values before updating lastProgressAt. This ensures stuck jobs returning the same 0 / 'Queued' values on every poll correctly trigger the 8-minute timeout instead of resetting it on every poll. --- src/pages/RepositoryAnalysis.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pages/RepositoryAnalysis.tsx b/src/pages/RepositoryAnalysis.tsx index a72c05e2..09ecd59f 100644 --- a/src/pages/RepositoryAnalysis.tsx +++ b/src/pages/RepositoryAnalysis.tsx @@ -203,8 +203,14 @@ export default function RepositoryAnalysis() { ); const nextJob = response.data.job || response.data; - // Reset lastProgressAt whenever progress changes - if (nextJob?.progressPercent !== undefined || nextJob?.progressMessage !== undefined) { + // Only reset lastProgressAt when progress actually changes value + // Comparing against current job state prevents resetting on unchanged + // fields like 0 / "Queued" which would prevent the timeout from firing + const prevPercent = job?.progressPercent ?? null; + const prevMessage = job?.progressMessage ?? null; + const nextPercent = nextJob?.progressPercent ?? null; + const nextMessage = nextJob?.progressMessage ?? null; + if (nextPercent !== prevPercent || nextMessage !== prevMessage) { lastProgressAt.current = Date.now(); } setJob(nextJob); From b9223c048428da400f152992f1a64ff5596910cf Mon Sep 17 00:00:00 2001 From: m4ninja Date: Fri, 22 May 2026 15:27:39 +0530 Subject: [PATCH 103/123] fix: use functional setJob to avoid stale closure in progress comparison Replace direct setJob(nextJob) with functional setJob(prevJob => ...) so lastProgressAt comparison always uses the latest job state, not the render-time snapshot captured by the polling closure. --- src/pages/RepositoryAnalysis.tsx | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/pages/RepositoryAnalysis.tsx b/src/pages/RepositoryAnalysis.tsx index 09ecd59f..b267a454 100644 --- a/src/pages/RepositoryAnalysis.tsx +++ b/src/pages/RepositoryAnalysis.tsx @@ -203,17 +203,19 @@ export default function RepositoryAnalysis() { ); const nextJob = response.data.job || response.data; - // Only reset lastProgressAt when progress actually changes value - // Comparing against current job state prevents resetting on unchanged - // fields like 0 / "Queued" which would prevent the timeout from firing - const prevPercent = job?.progressPercent ?? null; - const prevMessage = job?.progressMessage ?? null; - const nextPercent = nextJob?.progressPercent ?? null; - const nextMessage = nextJob?.progressMessage ?? null; - if (nextPercent !== prevPercent || nextMessage !== prevMessage) { - lastProgressAt.current = Date.now(); - } - setJob(nextJob); + // Use functional setJob so we always compare against the latest job + // state, avoiding the stale-closure bug where the polling loop holds + // an old snapshot of job and never sees progress-only updates. + setJob((prevJob) => { + const prevPercent = prevJob?.progressPercent ?? null; + const prevMessage = prevJob?.progressMessage ?? null; + const nextPercent = nextJob?.progressPercent ?? null; + const nextMessage = nextJob?.progressMessage ?? null; + if (nextPercent !== prevPercent || nextMessage !== prevMessage) { + lastProgressAt.current = Date.now(); + } + return nextJob; + }); if (nextJob?.status === "DONE") { pollingStartedAt.current = null; From 764d5f86fb3614e6c7b5659716d8ccc491156f2d Mon Sep 17 00:00:00 2001 From: pithva007 Date: Fri, 22 May 2026 16:01:29 +0530 Subject: [PATCH 104/123] feat: add skeleton loading states to analysis detail page (#61) --- app/analysis/[jobId]/page.tsx | 38 +++++++++++++- .../analysis/AnalysisDetailSkeleton.tsx | 52 +++++++++++++++++++ .../analysis/AnalysisFailureState.tsx | 14 +++++ .../repository/RepositoryOverview.tsx | 1 - 4 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 src/components/analysis/AnalysisDetailSkeleton.tsx create mode 100644 src/components/analysis/AnalysisFailureState.tsx diff --git a/app/analysis/[jobId]/page.tsx b/app/analysis/[jobId]/page.tsx index dd65cad6..d6a9841e 100644 --- a/app/analysis/[jobId]/page.tsx +++ b/app/analysis/[jobId]/page.tsx @@ -1,9 +1,43 @@ "use client"; -import { useRouter } from "next/navigation"; +import { useState, useEffect } from "react"; +import { useRouter, useParams } from "next/navigation"; +import { AnalysisDetailSkeleton } from "@/components/analysis/AnalysisDetailSkeleton"; +import { AnalysisFailureState } from "@/components/analysis/AnalysisFailureState"; export default function AnalysisJobPage() { const router = useRouter(); + const params = useParams(); + + const [data, setData] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (!params?.jobId) return; + + const token = localStorage.getItem("gitverse_token"); + + fetch(`/api/analysis-jobs/${params.jobId}`, { + headers: { + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + }) + .then(async (res) => { + if (!res.ok) { + const errData = await res.json().catch(() => null); + throw new Error(errData?.error || "Failed to load analysis"); + } + return res.json(); + }) + .then(setData) + .catch((e) => setError(e.message)) + .finally(() => setLoading(false)); + }, [params?.jobId]); + + if (loading) return ; + + if (error) return ; return (

- You haven't created any analysis jobs yet. + {data ? "Analysis job details will appear here." : "You haven't created any analysis jobs yet."}

-
+ return ( + + + + ); } diff --git a/app/signup/page.tsx b/app/signup/page.tsx index acb273ed..96ac54e2 100644 --- a/app/signup/page.tsx +++ b/app/signup/page.tsx @@ -1,7 +1,12 @@ 'use client' import Signup from '@/pages/Signup' +import { Suspense } from "react"; export default function SignupPage() { - return + return ( + + + + ); } diff --git a/lib/services/githubService.ts b/lib/services/githubService.ts index 5368419e..f6eb15eb 100644 --- a/lib/services/githubService.ts +++ b/lib/services/githubService.ts @@ -196,7 +196,7 @@ export class GitHubService { } } - const retryStatusCodes = [409, 502, 503, 504]; + const retryableCodes = [409, 502, 503, 504]; if ( (status && retryableCodes.includes(status)) || error.code === "ECONNABORTED" || diff --git a/src/components/visualizations/CommitActivityHeatmap.tsx b/src/components/visualizations/CommitActivityHeatmap.tsx index 294d9910..a677e430 100644 --- a/src/components/visualizations/CommitActivityHeatmap.tsx +++ b/src/components/visualizations/CommitActivityHeatmap.tsx @@ -57,29 +57,7 @@ export function CommitActivityHeatmap({ date: string; count: number; } | null>(null); - - // ========================================================= - // CRITICAL FIX: EARLY EXTENDED GUARD FOR PERFORMANCE & SAFETY - // ========================================================= - if (!repository?.commits || repository.commits.length === 0) { - return ( - -
-

Commit Activity

-

- Contribution activity over the last 52 weeks -

-
-
- -
-
- ); - } + const hasCommits = Boolean(repository?.commits && repository.commits.length > 0); useEffect(() => { // Advance the window automatically as time passes (refresh at next local midnight). @@ -101,6 +79,7 @@ export function CommitActivityHeatmap({ useEffect(() => { if (!svgRef.current) return; + if (!hasCommits) return; const data = generateCommitData(repository.commits, now); const svg = d3.select(svgRef.current); @@ -352,7 +331,7 @@ export function CommitActivityHeatmap({ .attr("fill", "currentColor") .attr("font-size", "10px") .text("More"); - }, [repository, now]); + }, [repository, now, hasCommits]); const getCommitsForDate = (date: string) => { return ( @@ -369,6 +348,26 @@ export function CommitActivityHeatmap({ ? getCommitsForDate(selectedDate.date) : []; + if (!hasCommits) { + return ( + +
+

Commit Activity

+

+ Contribution activity over the last 52 weeks +

+
+
+ +
+
+ ); + } + return (
@@ -475,4 +474,4 @@ export function CommitActivityHeatmap({ /> ); -} \ No newline at end of file +} diff --git a/src/pages/RepositoryAnalysis.tsx b/src/pages/RepositoryAnalysis.tsx index b267a454..915e3b73 100644 --- a/src/pages/RepositoryAnalysis.tsx +++ b/src/pages/RepositoryAnalysis.tsx @@ -83,11 +83,11 @@ export default function RepositoryAnalysis() { const [elapsedSeconds, setElapsedSeconds] = useState(0); const pollingStartedAt = useRef(null); - // Tracks last time progress changed — prevents falsely timing out active jobs + // Tracks last time progress changed — prevents falsely timing out active jobs const lastProgressAt = useRef(null); const elapsedTimer = useRef(null); - // ── Elapsed seconds ticker ──────────────────────────────────────── + // ââ€â‚¬Ã¢â€â‚¬ Elapsed seconds ticker ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬ useEffect(() => { if (isAnalyzing && !analysisTimedOut) { elapsedTimer.current = setInterval(() => { @@ -105,12 +105,12 @@ export default function RepositoryAnalysis() { }; }, [isAnalyzing, analysisTimedOut]); - // ── Initial fetch ───────────────────────────────────────────────── + // ââ€â‚¬Ã¢â€â‚¬ Initial fetch ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬ useEffect(() => { fetchRepository(); }, [id]); - // ── Job polling ─────────────────────────────────────────────────── + // ââ€â‚¬Ã¢â€â‚¬ Job polling ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬ useEffect(() => { const repoStatus = repository?.status as string | undefined; const jobStatus = job?.status as string | undefined; @@ -142,7 +142,7 @@ export default function RepositoryAnalysis() { const poll = async () => { if (stopped) return; - // ── Timeout guard ────────────────────────────────────────── + // ââ€â‚¬Ã¢â€â‚¬ Timeout guard ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬ if ( lastProgressAt.current && Date.now() - lastProgressAt.current > ANALYSIS_TIMEOUT_MS @@ -173,7 +173,7 @@ export default function RepositoryAnalysis() { // analysisTimedOut included so Check Again restarts polling }, [repository?.status, repository?.latestJob?.id, job?.id, job?.status, analysisTimedOut]); - // ── Data fetchers ───────────────────────────────────────────────── + // ââ€â‚¬Ã¢â€â‚¬ Data fetchers ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬ const fetchRepository = async () => { if (!id) return; try { @@ -206,7 +206,7 @@ export default function RepositoryAnalysis() { // Use functional setJob so we always compare against the latest job // state, avoiding the stale-closure bug where the polling loop holds // an old snapshot of job and never sees progress-only updates. - setJob((prevJob) => { + setJob((prevJob: any) => { const prevPercent = prevJob?.progressPercent ?? null; const prevMessage = prevJob?.progressMessage ?? null; const nextPercent = nextJob?.progressPercent ?? null; @@ -237,7 +237,7 @@ export default function RepositoryAnalysis() { } }; - // ── Delete ──────────────────────────────────────────────────────── + // ---------------- Delete ---------------- const handleDeleteRepository = async () => { if (!id) return; setIsDeleting(true); @@ -264,7 +264,7 @@ export default function RepositoryAnalysis() { } }; - // ── Tab content ─────────────────────────────────────────────────── + // ââ€â‚¬Ã¢â€â‚¬ Tab content ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬ const renderContent = () => { switch (activeTab) { case "overview": return ; @@ -277,7 +277,7 @@ export default function RepositoryAnalysis() { } }; - // ── Helpers ─────────────────────────────────────────────────────── + // ââ€â‚¬Ã¢â€â‚¬ Helpers ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬ const formatElapsed = (secs: number) => { if (secs < 60) return `${secs}s`; return `${Math.floor(secs / 60)}m ${secs % 60}s`; @@ -286,7 +286,7 @@ export default function RepositoryAnalysis() { const progressPercent = job?.progressPercent ?? 0; const progressMessage = job?.progressMessage || "Queued"; - // ── Render ──────────────────────────────────────────────────────── + // ââ€â‚¬Ã¢â€â‚¬ Render ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬ return (
@@ -323,7 +323,7 @@ export default function RepositoryAnalysis() {

{isAnalyzing && !analysisTimedOut && ( - ◉ + â—‰ Analyzing... )} @@ -338,7 +338,7 @@ export default function RepositoryAnalysis() {
- {/* ── Analyzing spinner (with timeout awareness) ── */} + {/* ââ€â‚¬Ã¢â€â‚¬ Analyzing spinner (with timeout awareness) ââ€â‚¬Ã¢â€â‚¬ */} {isAnalyzing && !analysisTimedOut ? (
@@ -378,7 +378,7 @@ export default function RepositoryAnalysis() {

Still queued after {formatElapsed(elapsedSeconds)}. - The worker runs every 5 minutes via GitHub Actions — + The worker runs every 5 minutes via GitHub Actions  it should pick this up shortly.

@@ -398,7 +398,7 @@ export default function RepositoryAnalysis() {
) : analysisTimedOut || analysisError ? ( - /* ── Timeout / error state ── */ + /* ââ€â‚¬Ã¢â€â‚¬ Timeout / error state ââ€â‚¬Ã¢â€â‚¬ */
@@ -439,7 +439,7 @@ export default function RepositoryAnalysis() {
) : ( - /* ── Done — show tabs ── */ + /* ââ€â‚¬Ã¢â€â‚¬ Done  show tabs ââ€â‚¬Ã¢â€â‚¬ */ <>
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index ec1fbba8..b615854e 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,4 +1,5 @@ import type { AppProps } from 'next/app' +import { SessionProvider } from 'next-auth/react' import { ThemeProvider } from '@/context/ThemeContext' import { AuthProvider } from '@/contexts/AuthContext' import { Toaster } from '@/components/ui/toaster' @@ -6,11 +7,13 @@ import '@/app/globals.css' export default function App({ Component, pageProps }: AppProps) { return ( - - - - - - + + + + + + + + ) } From a5d5c2ff85dc060e7f20a7975d880afde0536a8e Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Fri, 22 May 2026 16:36:27 +0530 Subject: [PATCH 109/123] feat: improve cron runner time budget handling and observability --- app/api/internal/run-analysis/route.ts | 5 +++-- scripts/analysisWorker.ts | 31 +++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/app/api/internal/run-analysis/route.ts b/app/api/internal/run-analysis/route.ts index 902bc17d..69f4e80f 100644 --- a/app/api/internal/run-analysis/route.ts +++ b/app/api/internal/run-analysis/route.ts @@ -17,9 +17,10 @@ export async function GET(request: Request) { console.log('Starting analysis cron run...'); try { - // Run the worker loop in "once" mode so it returns after one pass through the queue + // Run the worker loop with a time budget slightly less than Vercel's maxDuration const metrics = await startAnalysisWorkerLoop({ - once: true + timeBudgetMs: 280_000, // 280 seconds (leaves 20s buffer) + stopOnEmptyQueue: true }); console.log(`Finished analysis cron run. Summary:`, metrics); diff --git a/scripts/analysisWorker.ts b/scripts/analysisWorker.ts index e105bfa7..69bff77d 100644 --- a/scripts/analysisWorker.ts +++ b/scripts/analysisWorker.ts @@ -137,6 +137,8 @@ export interface AnalysisWorkerSummary { jobsFailed: number; executionDurationMs: number; success: boolean; + budgetExhausted?: boolean; + earlyStopReason?: string; } export async function startAnalysisWorkerLoop(opts?: { @@ -145,6 +147,9 @@ export async function startAnalysisWorkerLoop(opts?: { heartbeatIntervalMs?: number; lockMs?: number; once?: boolean; + timeBudgetMs?: number; + budgetGraceMs?: number; + stopOnEmptyQueue?: boolean; }): Promise { const workerId = opts?.workerId || getWorkerId(); const pollIntervalMs = opts?.pollIntervalMs ?? POLL_INTERVAL_MS; @@ -160,6 +165,11 @@ export async function startAnalysisWorkerLoop(opts?: { let jobsProcessed = 0; let jobsSkipped = 0; let jobsFailed = 0; + + const timeBudgetMs = opts?.timeBudgetMs; + const budgetGraceMs = opts?.budgetGraceMs ?? 10_000; // 10s default buffer + let budgetExhausted = false; + let earlyStopReason: string | undefined; const shutdown = async (signal: string) => { if (stopping) return; @@ -177,6 +187,17 @@ export async function startAnalysisWorkerLoop(opts?: { process.on("SIGINT", () => void shutdown("SIGINT")); while (!stopping) { + if (timeBudgetMs) { + const elapsed = Date.now() - startTimeMs; + const remaining = timeBudgetMs - elapsed; + if (remaining <= budgetGraceMs) { + console.log(`Time budget nearly exhausted (${remaining}ms remaining). Stopping gracefully.`); + budgetExhausted = true; + earlyStopReason = "time_budget_exhausted"; + break; + } + } + try { const job = await analysisJobService.claimNextJob({ workerId, @@ -185,7 +206,11 @@ export async function startAnalysisWorkerLoop(opts?: { if (!job) { jobsSkipped++; - if (opts?.once) break; + if (opts?.once || opts?.stopOnEmptyQueue) { + console.log(`Queue empty. Stopping gracefully.`); + earlyStopReason = "queue_empty"; + break; + } await sleep(pollIntervalMs); continue; } @@ -213,6 +238,8 @@ export async function startAnalysisWorkerLoop(opts?: { jobsFailed, executionDurationMs: Date.now() - startTimeMs, success: false, + budgetExhausted, + earlyStopReason, }; } await sleep(pollIntervalMs); @@ -226,6 +253,8 @@ export async function startAnalysisWorkerLoop(opts?: { jobsFailed, executionDurationMs: Date.now() - startTimeMs, success: true, + budgetExhausted, + earlyStopReason, }; } From 41c9b588fe4ff05c36fd9b483a3fad6a32e14626 Mon Sep 17 00:00:00 2001 From: pithva007 Date: Fri, 22 May 2026 16:38:36 +0530 Subject: [PATCH 110/123] Resolved changes. --- app/analysis/[jobId]/page.tsx | 75 ++++++++++++++++++++++--------- lib/services/repositoryService.ts | 12 ++--- 2 files changed, 60 insertions(+), 27 deletions(-) diff --git a/app/analysis/[jobId]/page.tsx b/app/analysis/[jobId]/page.tsx index dda975e3..d2710286 100644 --- a/app/analysis/[jobId]/page.tsx +++ b/app/analysis/[jobId]/page.tsx @@ -12,24 +12,25 @@ export default function AnalysisJobPage() { const router = useRouter(); const params = useParams(); - const [_data, setData] = useState(null); + const [data, setData] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { + setLoading(true); + setData(null); + setError(null); + if (!params?.jobId) { setLoading(false); // prevent skeleton from getting stuck return; } - let cancelled = false; - setLoading(true); - setData(null); - setError(null); - + const controller = new AbortController(); const token = localStorage.getItem("gitverse_token"); fetch(`/api/analysis-jobs/${params.jobId}`, { + signal: controller.signal, headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}), }, @@ -49,17 +50,21 @@ export default function AnalysisJobPage() { return res.json(); }) .then((result) => { - if (!cancelled) setData(result); + if (!controller.signal.aborted) { + setData(result); + setLoading(false); + } }) .catch((e) => { - if (!cancelled) setError(e.message); - }) - .finally(() => { - if (!cancelled) setLoading(false); + if (e.name === 'AbortError') return; + if (!controller.signal.aborted) { + setError(e.message); + setLoading(false); + } }); return () => { - cancelled = true; + controller.abort(); }; }, [params?.jobId]); @@ -69,15 +74,43 @@ export default function AnalysisJobPage() { return ( -
- router.push("/analyze")} - /> -
+ {data ? ( +
+

{data.title || "Analysis Job Details"}

+
+
+
+

Status

+

{data.status || "Unknown"}

+
+
+

Repository

+

{data.repository || "Unknown"}

+
+
+

Created At

+

{data.createdAt ? new Date(data.createdAt).toLocaleString() : "Unknown"}

+
+
+
+
+

Raw Data

+
+              {JSON.stringify(data, null, 2)}
+            
+
+
+ ) : ( +
+ router.push("/analyze")} + /> +
+ )}
); } diff --git a/lib/services/repositoryService.ts b/lib/services/repositoryService.ts index 9e00a25a..bfec56d1 100644 --- a/lib/services/repositoryService.ts +++ b/lib/services/repositoryService.ts @@ -252,12 +252,12 @@ export class RepositoryService { const existingCommits = commits.length > 0 ? await prisma.commit.findMany({ - where: { - repositoryId, - hash: { in: commits.map((c) => c.hash) }, - }, - select: { hash: true }, - }) + where: { + repositoryId, + hash: { in: commits.map((c) => c.hash) }, + }, + select: { hash: true }, + }) : []; const existingHashes = new Set(existingCommits.map((c) => c.hash)); From be6d53b18fd6ef74da8221d1880ea7d1f679f2cb Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Fri, 22 May 2026 16:41:29 +0530 Subject: [PATCH 111/123] docs: document GitHub App permissions and environment setup --- README.md | 47 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index be567a7a..23d73802 100644 --- a/README.md +++ b/README.md @@ -299,18 +299,19 @@ cp .env.example .env.local | `GOOGLE_CLIENT_ID` | Google OAuth 2.0 client ID | [Google Cloud Console](https://console.cloud.google.com) → **APIs & Services → Credentials → Create Credentials → OAuth client ID** (Web application). | | `GOOGLE_CLIENT_SECRET` | Google OAuth 2.0 client secret | Obtained alongside `GOOGLE_CLIENT_ID` in the same step above. | -### GitHub App Configuration (for PR reviews) +### GitHub App Configuration -GitVerse uses a GitHub App to analyze repositories and post PR reviews. +GitVerse integrates heavily with GitHub via a GitHub App to read repositories, post PR reviews, and track issues. -**Required Permissions:** -When creating your GitHub App, ensure you grant the following permissions: -- **Repository Permissions**: - - `Contents`: Read-only (Required to fetch repository code for analysis) - - `Metadata`: Read-only (Mandatory for all GitHub Apps) - - `Pull requests`: Read & Write (Required to read PR changes and post review comments) - - `Issues`: Read & Write (Required if tracking or commenting on issues) -- **Subscribe to events**: `Pull request` +**Required GitHub App Permissions & Scopes:** +To ensure full functionality, your GitHub App must be granted the following repository-level permissions: +- **Contents:** Read-only (Required to fetch source code for AI analysis) +- **Metadata:** Read-only (Mandatory basic read access for all GitHub Apps) +- **Pull requests:** Read & Write (Required to read PR diffs and post review comments) +- **Issues:** Read & Write (Required for tracking and commenting on issues) + +**Required Webhook Events:** +- `Pull request` (Fires when PRs are opened or updated, triggering the AI review service) | Variable | Description & Usage | How to obtain | | :--- | :--- | :--- | @@ -319,6 +320,12 @@ When creating your GitHub App, ensure you grant the following permissions: | `GITHUB_APP_SLUG` | URL slug of your GitHub App. Used to generate installation URLs. | The part after `github.com/apps/` in the App's public URL. | | `GITHUB_WEBHOOK_SECRET` | Secret string. Used to verify that incoming webhook payloads genuinely came from GitHub. | Set any strong random string here and enter the same value in your GitHub App's webhook configuration. | +**Local Development Setup for GitHub Apps:** +1. Create a GitHub App in your developer settings. +2. Use a service like ngrok (`ngrok http 3000`) to expose your local server. +3. Set your App's Webhook URL to `https:///api/webhooks/github`. +4. Add the App credentials to `.env.local`. + ### Optional Variables | Variable | Description | Default | @@ -430,11 +437,21 @@ npm run dev -- -p 3001 **Symptoms:** PR reviews aren't posting, repo analysis fails, or webhook errors. **Fix:** -- **Missing Credentials:** Ensure `GITHUB_APP_ID`, `GITHUB_APP_PRIVATE_KEY`, and other related variables are correctly populated in your `.env.local` or Vercel environment settings. Without these, PR reviews and repository analysis will fail. Build or deployment failures caused by missing secrets are usually flagged during the `npm run build` step. -- **Permission Denied:** Verify your GitHub App has the exact permissions listed in the [GitHub App Configuration](#github-app-configuration-for-pr-reviews) section (especially Read/Write on Pull Requests and Read on Contents). If you updated permissions on an existing App, you must accept the new permissions on your App installations. -- **Invalid Callback URL / Webhook:** Ensure the webhook URL in the GitHub App settings exactly matches your deployed domain's endpoint (e.g., `https:///api/integrations/github/webhook`). -- **Vercel Environment Setup:** In Vercel, format the `GITHUB_APP_PRIVATE_KEY` correctly. Sometimes newlines get mangled during copy-pasting. Enclosing the key in double quotes in the Vercel dashboard or ensuring literal `\n` characters are used can prevent parsing errors. -- **Error Handling (Local Dev):** If running locally, check your terminal for missing environment variable warnings at startup. If analysis fails silently, review the terminal logs for explicit GitHub API permission or authentication errors. +- **Missing GitHub App Credentials:** Ensure `GITHUB_APP_ID`, `GITHUB_APP_PRIVATE_KEY`, and other related variables are correctly populated in your `.env.local` or Vercel environment settings. Without these, PR reviews and repository analysis will fail. Build or deployment failures caused by missing secrets are usually flagged during the `npm run build` step. +- **Invalid GitHub App IDs:** Ensure your `GITHUB_APP_ID` is a numeric string (not the client ID or slug). +- **Incorrect Callback URLs:** Ensure the webhook URL in the GitHub App settings exactly matches your deployed domain's endpoint (e.g., `https:///api/webhooks/github`). +- **Permission Denied / API Access Errors:** Verify your GitHub App has the exact permissions listed in the [GitHub App Configuration](#github-app-configuration) section (especially Read/Write on Pull Requests and Read on Contents). If you updated permissions on an existing App, you must accept the new permissions on your App installations. +- **Vercel Deployment / Environment Setup Issues:** In Vercel, format the `GITHUB_APP_PRIVATE_KEY` correctly. Sometimes newlines get mangled during copy-pasting. Enclosing the key in double quotes in the Vercel dashboard or ensuring literal `\n` characters are used can prevent parsing errors. +- **Authentication/Session Failures:** If `next-auth` sessions fail to read the GitHub token, ensure `NEXTAUTH_SECRET` is set and matches across your infrastructure. + +### Error-Handling Guidance for Developers + +When extending the GitHub integration or building new features, follow these error-handling practices: + +- **Detecting Missing Env Vars:** At application startup or within route handlers, explicitly check for `GITHUB_APP_ID` and `GITHUB_APP_PRIVATE_KEY`. If missing, log a clear warning and gracefully disable the GitHub-dependent features instead of crashing the server or UI. +- **Safe API Validation:** Always wrap `githubService` or `octokit` calls in `try/catch` blocks. If `octokit.request` throws a `403 Forbidden` or `404 Not Found` (which often indicates a permission issue or missing repository access), handle it cleanly by returning a `403` or `404` JSON response with a safe, descriptive error string (e.g., `"Repository access denied. Check GitHub App permissions."`). +- **Runtime Failure Behavior:** Ensure webhooks respond with a `200 OK` quickly, even if the internal AI analysis fails or throws an error. If you return a `500` to GitHub synchronously, they may disable your webhook after repeated failures. Use background jobs (like the analysis worker) or isolated error logging for the actual processing step. +- **Common Integration Mistakes:** The most frequent developer mistake is forgetting to accept new permissions on the GitHub App installation after updating them in the App settings. Always remind users to navigate to their installation settings to approve permission scope changes. ## 🤠Contributing From 78124ec47da08ac7d0a59bd2494a272ef6a5c7ac Mon Sep 17 00:00:00 2001 From: Atul Upadhyay Date: Fri, 22 May 2026 16:43:28 +0530 Subject: [PATCH 112/123] Add missing requireAuth and isHttpError to middleware requireAuth, isHttpError, and getAuthUser were imported across 59 API route files but never defined. Implement them so that every protected route properly verifies the JWT Bearer token and extracts the authenticated user ID for database query scoping. --- lib/middleware.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/lib/middleware.ts b/lib/middleware.ts index abf04a9c..dd6e2b33 100644 --- a/lib/middleware.ts +++ b/lib/middleware.ts @@ -1,6 +1,47 @@ import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import { getToken } from "next-auth/jwt"; +import { verifyToken } from "./auth"; + +export class HttpError extends Error { + status: number; + constructor(message: string, status: number) { + super(message); + this.name = "HttpError"; + this.status = status; + } +} + +export function isHttpError(error: unknown): error is HttpError { + return error instanceof HttpError; +} + +export async function requireAuth( + request: NextRequest +): Promise<{ userId: number }> { + const authHeader = request.headers.get("authorization"); + if (!authHeader || !authHeader.startsWith("Bearer ")) { + throw new HttpError("Authentication required", 401); + } + + const token = authHeader.slice(7); + const payload = verifyToken(token); + if (!payload) { + throw new HttpError("Invalid or expired token", 401); + } + + return { userId: payload.userId }; +} + +export async function getAuthUser( + request: NextRequest +): Promise<{ userId: number } | null> { + try { + return await requireAuth(request); + } catch { + return null; + } +} export async function middleware(request: NextRequest) { try { From c47622669a41674ce43e775ab103b2261aff62c1 Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Fri, 22 May 2026 16:48:17 +0530 Subject: [PATCH 113/123] feat: improve analysis job progress reporting and percentages --- lib/services/repositoryService.ts | 129 ++++++++++++++++-------------- 1 file changed, 70 insertions(+), 59 deletions(-) diff --git a/lib/services/repositoryService.ts b/lib/services/repositoryService.ts index 9e00a25a..f98f687a 100644 --- a/lib/services/repositoryService.ts +++ b/lib/services/repositoryService.ts @@ -22,6 +22,56 @@ export type RepositoryAnalysisProgressReporter = ( update: RepositoryAnalysisProgress, ) => void | Promise; +class AnalysisProgressTracker { + constructor( + private repositoryId: number, + private reporter?: RepositoryAnalysisProgressReporter + ) {} + + async update(percent: number, message: string, details?: unknown) { + const safePercent = Math.max(0, Math.min(100, Math.round(percent || 0))); + console.log(`[Repo ${this.repositoryId}] ${safePercent}% - ${message}`); + + if (!this.reporter) return; + try { + await this.reporter({ + progressPercent: safePercent, + progressMessage: message, + progressDetails: details, + }); + } catch { + // Progress reporting must never break analysis + } + } + + async progressSubTask( + startPercent: number, + endPercent: number, + current: number, + total: number, + message: string + ) { + if (total <= 0) { + await this.update(endPercent, `${message} (Completed)`); + return; + } + const range = endPercent - startPercent; + const ratio = Math.max(0, Math.min(1, current / total)); + const currentPercent = startPercent + (range * ratio); + await this.update(currentPercent, message); + } + + async fail(error: Error | unknown) { + const msg = error instanceof Error ? error.message : "Unknown error"; + console.error(`[Repo ${this.repositoryId}] Analysis Failed: ${msg}`); + if (this.reporter) { + try { + await this.reporter({ progressMessage: `Failed: ${msg}` }); + } catch {} + } + } +} + export class RepositoryService { private async tryReadmeFromRepoPath(repoPath: string): Promise<{ path: string; @@ -168,16 +218,8 @@ export class RepositoryService { data: { status: "analyzing" }, }); - const report = async (update: RepositoryAnalysisProgress) => { - if (!opts?.onProgress) return; - try { - await opts.onProgress(update); - } catch { - // Progress reporting must never break analysis. - } - }; - - await report({ progressPercent: 1, progressMessage: "Starting" }); + const tracker = new AnalysisProgressTracker(repositoryId, opts?.onProgress); + await tracker.update(1, "Starting analysis"); // Create temporary directory for cloning const tempDir = path.join( @@ -190,15 +232,11 @@ export class RepositoryService { try { // Clone repository - console.log(`Cloning repository ${repository.url} to ${tempDir}`); - await report({ - progressPercent: 5, - progressMessage: "Cloning repository", - }); + await tracker.update(5, `Cloning repository ${repository.url}`); gitService = await GitService.cloneRepository(repository.url, tempDir); // Capture README early (best-effort) - await report({ progressPercent: 8, progressMessage: "Reading README" }); + await tracker.update(8, "Reading README"); const readme = await this.tryReadmeFromRepoPath(tempDir); await prisma.repository.update({ where: { id: repositoryId }, @@ -210,18 +248,11 @@ export class RepositoryService { }); // Get repository size - await report({ - progressPercent: 10, - progressMessage: "Calculating size", - }); + await tracker.update(10, "Calculating size"); const size = await gitService.getRepositorySize(); // Analyze branches - console.log(`Analyzing branches for repository ${repositoryId}`); - await report({ - progressPercent: 15, - progressMessage: "Analyzing branches", - }); + await tracker.update(15, "Analyzing branches"); const branches = await gitService.getBranches(); const defaultBranch = branches.find((b) => b.isDefault)?.name || "main"; @@ -238,11 +269,7 @@ export class RepositoryService { }); // Analyze commits from all branches - console.log(`Analyzing commits for repository ${repositoryId}`); - await report({ - progressPercent: 25, - progressMessage: "Reading commit history", - }); + await tracker.update(25, "Reading commit history"); const commits = await gitService.getCommits("--all", 1000); console.log(`Total commits fetched from git: ${commits.length}`); @@ -273,9 +300,13 @@ export class RepositoryService { let insertedCount = 0; let failedCount = 0; - const totalNewCommits = Math.max(1, newCommits.length); + const totalNewCommits = newCommits.length; let lastCommitProgressUpdateAt = Date.now(); + if (totalNewCommits === 0) { + await tracker.update(60, "Storing commits (Up to date)"); + } + for (const commit of newCommits) { try { const createdCommit = await prisma.commit.create({ @@ -302,11 +333,7 @@ export class RepositoryService { // Periodically report progress (every ~2s) if (Date.now() - lastCommitProgressUpdateAt > 2000) { - const pct = 25 + Math.round((insertedCount / totalNewCommits) * 35); - await report({ - progressPercent: Math.min(60, pct), - progressMessage: `Storing commits (${insertedCount}/${newCommits.length})`, - }); + await tracker.progressSubTask(25, 60, insertedCount, totalNewCommits, `Storing commits (${insertedCount}/${totalNewCommits})`); lastCommitProgressUpdateAt = Date.now(); } @@ -338,8 +365,7 @@ export class RepositoryService { ); // Analyze files - console.log(`Analyzing file tree for repository ${repositoryId}`); - await report({ progressPercent: 65, progressMessage: "Scanning files" }); + await tracker.update(65, "Scanning files"); const files = await gitService.getFileTree(); // Avoid querying existing file paths (can be huge). Just rely on @@ -362,25 +388,17 @@ export class RepositoryService { }); const insertedSoFar = Math.min(files.length, i + chunk.length); - await report({ - progressPercent: - 65 + Math.round((insertedSoFar / files.length) * 10), - progressMessage: `Storing files (${insertedSoFar}/${files.length})`, - }); + await tracker.progressSubTask(65, 75, insertedSoFar, files.length, `Storing files (${insertedSoFar}/${files.length})`); } console.log( `File scan complete: processed ${files.length} paths for repository ${repositoryId}`, ); } else { - console.log(`No files found for repository ${repositoryId}`); + await tracker.update(75, "Storing files (Skipped - no files found)"); } // Analyze contributors - console.log(`Analyzing contributors for repository ${repositoryId}`); - await report({ - progressPercent: 80, - progressMessage: "Analyzing contributors", - }); + await tracker.update(80, "Analyzing contributors"); const contributors = await gitService.getContributors(); const totalContributions = contributors.reduce( (sum, c) => sum + c.commits, @@ -411,11 +429,7 @@ export class RepositoryService { } // Detect languages - console.log(`Detecting languages for repository ${repositoryId}`); - await report({ - progressPercent: 90, - progressMessage: "Detecting languages", - }); + await tracker.update(90, "Detecting languages"); const languages = await gitService.detectLanguages(); // Languages to ignore (config/data formats, not actual code) @@ -483,16 +497,13 @@ export class RepositoryService { }, }); - await report({ progressPercent: 100, progressMessage: "Completed" }); - - console.log(`Repository ${repositoryId} analysis completed`); + await tracker.update(100, "Completed"); } catch (error: any) { - console.error(`Error analyzing repository ${repositoryId}:`, error); await prisma.repository.update({ where: { id: repositoryId }, data: { status: "failed" }, }); - await report({ progressMessage: "Failed" }); + await tracker.fail(error); throw error; } finally { // Cleanup cloned repository From b03426e71f793a964ac5fd175d19f719178295c1 Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Fri, 22 May 2026 16:52:02 +0530 Subject: [PATCH 114/123] fix: remove related analysis jobs during repository deletion --- lib/services/repositoryService.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/services/repositoryService.ts b/lib/services/repositoryService.ts index 9e00a25a..29d3bdd9 100644 --- a/lib/services/repositoryService.ts +++ b/lib/services/repositoryService.ts @@ -576,9 +576,16 @@ export class RepositoryService { throw new Error("Repository not found"); } - await prisma.repository.delete({ - where: { id }, - }); + // Delete related analysis jobs and the repository in a transaction + // to ensure no orphaned rows remain if the process is interrupted. + await prisma.$transaction([ + prisma.analysisJob.deleteMany({ + where: { repositoryId: id }, + }), + prisma.repository.delete({ + where: { id }, + }), + ]); return { success: true }; } From 2df53eb49da2bd8b06130d7a31bc332db8fcbbdd Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Fri, 22 May 2026 16:56:30 +0530 Subject: [PATCH 115/123] fix: prevent caching of user profile API responses --- app/api/users/me/route.ts | 42 +++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/app/api/users/me/route.ts b/app/api/users/me/route.ts index 9522db71..36f86630 100644 --- a/app/api/users/me/route.ts +++ b/app/api/users/me/route.ts @@ -27,23 +27,43 @@ export async function GET(request: NextRequest) { })) > 0; if (!userDetails) { - return NextResponse.json({ message: "User not found" }, { status: 404 }); + return NextResponse.json( + { message: "User not found" }, + { + status: 404, + headers: { + "Cache-Control": "no-store", + }, + } + ); } - return NextResponse.json({ - id: userDetails.id, - name: userDetails.name, - email: userDetails.email, - image: userDetails.image, - createdAt: userDetails.createdAt, - avatarUrl: (userDetails as any).image, - isGoogleLinked: hasGoogleAccount, - }); + return NextResponse.json( + { + id: userDetails.id, + name: userDetails.name, + email: userDetails.email, + image: userDetails.image, + createdAt: userDetails.createdAt, + avatarUrl: (userDetails as any).image, + isGoogleLinked: hasGoogleAccount, + }, + { + headers: { + "Cache-Control": "no-store", + }, + } + ); } catch (error: any) { console.error("Error fetching user:", sanitizeErrorMessage(error)); return NextResponse.json( { message: "Failed to fetch user" }, - { status: 500 } + { + status: 500, + headers: { + "Cache-Control": "no-store", + }, + } ); } } From 3f27713a83803b2daf1eb89bed02762471b54b5b Mon Sep 17 00:00:00 2001 From: pithva007 Date: Fri, 22 May 2026 17:15:48 +0530 Subject: [PATCH 116/123] fix(docs): add accessible empty state with CTA --- app/api/auth/login/route.ts | 2 +- app/api/auth/signup/route.ts | 2 +- src/components/docs/DocsEmptyState.tsx | 47 +++++++++++++++++++ src/components/docs/index.ts | 1 + .../repository/RepositoryOverview.tsx | 1 - .../visualizations/CommitActivityHeatmap.tsx | 44 +++++++++-------- src/pages/RepositoryAnalysis.tsx | 2 +- 7 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 src/components/docs/DocsEmptyState.tsx create mode 100644 src/components/docs/index.ts diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts index a7cbb052..e92ca7f7 100644 --- a/app/api/auth/login/route.ts +++ b/app/api/auth/login/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { checkRateLimit } from '@/src/utils/rateLimit'; +import { checkRateLimit } from '@/utils/rateLimit'; import bcrypt from "bcryptjs"; import prisma from "@/lib/prisma"; import { generateToken } from "@/lib/auth"; diff --git a/app/api/auth/signup/route.ts b/app/api/auth/signup/route.ts index e774126d..82589b36 100644 --- a/app/api/auth/signup/route.ts +++ b/app/api/auth/signup/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { checkRateLimit } from '@/src/utils/rateLimit'; +import { checkRateLimit } from '@/utils/rateLimit'; import bcrypt from "bcryptjs"; import prisma from "@/lib/prisma"; import { generateToken } from "@/lib/auth"; diff --git a/src/components/docs/DocsEmptyState.tsx b/src/components/docs/DocsEmptyState.tsx new file mode 100644 index 00000000..83b15ba4 --- /dev/null +++ b/src/components/docs/DocsEmptyState.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import Link from 'next/link'; +import { FileText, LucideIcon } from 'lucide-react'; +import { Button } from '@/components/ui/Button'; + +interface DocsEmptyStateProps { + title?: string; + description?: string; + buttonText?: string; + buttonLink?: string; + icon?: LucideIcon; +} + +export function DocsEmptyState({ + title = "No documentation found", + description = "Get started by creating your first document or exploring the setup guide.", + buttonText = "Open Getting Started Guide", + buttonLink = "/docs", + icon: Icon = FileText, +}: DocsEmptyStateProps) { + return ( +
+
+
+

+ {title} +

+

+ {description} +

+ {buttonText && buttonLink && ( + + )} +
+ ); +} diff --git a/src/components/docs/index.ts b/src/components/docs/index.ts new file mode 100644 index 00000000..cc13e8a0 --- /dev/null +++ b/src/components/docs/index.ts @@ -0,0 +1 @@ +export { DocsEmptyState } from './DocsEmptyState'; diff --git a/src/components/repository/RepositoryOverview.tsx b/src/components/repository/RepositoryOverview.tsx index bb061d23..c842fec6 100644 --- a/src/components/repository/RepositoryOverview.tsx +++ b/src/components/repository/RepositoryOverview.tsx @@ -25,7 +25,6 @@ import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import rehypeRaw from "rehype-raw"; import rehypeSanitize, { defaultSchema } from "rehype-sanitize"; -import { EmptyState } from "@/components/ui/EmptyState"; interface RepositoryData { id: string; diff --git a/src/components/visualizations/CommitActivityHeatmap.tsx b/src/components/visualizations/CommitActivityHeatmap.tsx index 294d9910..a50e250c 100644 --- a/src/components/visualizations/CommitActivityHeatmap.tsx +++ b/src/components/visualizations/CommitActivityHeatmap.tsx @@ -61,25 +61,7 @@ export function CommitActivityHeatmap({ // ========================================================= // CRITICAL FIX: EARLY EXTENDED GUARD FOR PERFORMANCE & SAFETY // ========================================================= - if (!repository?.commits || repository.commits.length === 0) { - return ( - -
-

Commit Activity

-

- Contribution activity over the last 52 weeks -

-
-
- -
-
- ); - } + const hasNoCommits = !repository?.commits || repository.commits.length === 0; useEffect(() => { // Advance the window automatically as time passes (refresh at next local midnight). @@ -102,6 +84,10 @@ export function CommitActivityHeatmap({ useEffect(() => { if (!svgRef.current) return; + if (!repository?.commits || repository.commits.length === 0) { + return; + } + const data = generateCommitData(repository.commits, now); const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); @@ -369,6 +355,26 @@ export function CommitActivityHeatmap({ ? getCommitsForDate(selectedDate.date) : []; + if (hasNoCommits) { + return ( + +
+

Commit Activity

+

+ Contribution activity over the last 52 weeks +

+
+
+ +
+
+ ); + } + return (
diff --git a/src/pages/RepositoryAnalysis.tsx b/src/pages/RepositoryAnalysis.tsx index b267a454..f13c6ac6 100644 --- a/src/pages/RepositoryAnalysis.tsx +++ b/src/pages/RepositoryAnalysis.tsx @@ -83,7 +83,7 @@ export default function RepositoryAnalysis() { const [elapsedSeconds, setElapsedSeconds] = useState(0); const pollingStartedAt = useRef(null); - // Tracks last time progress changed — prevents falsely timing out active jobs + // Tracks last time progress changed prevents falsely timing out active jobs const lastProgressAt = useRef(null); const elapsedTimer = useRef(null); From be3f9ab0a19c6557490b278454471bf9e619fb49 Mon Sep 17 00:00:00 2001 From: Rial1608 Date: Fri, 22 May 2026 17:17:50 +0530 Subject: [PATCH 117/123] fix: enforce ownership, disable caching, improve validation, and add structured logging --- app/api/ai/analyze-repository/route.ts | 246 ++++++++++++++++--------- package-lock.json | 15 +- 2 files changed, 159 insertions(+), 102 deletions(-) diff --git a/app/api/ai/analyze-repository/route.ts b/app/api/ai/analyze-repository/route.ts index 88cc06d9..e9fd2913 100644 --- a/app/api/ai/analyze-repository/route.ts +++ b/app/api/ai/analyze-repository/route.ts @@ -3,121 +3,183 @@ import { isHttpError, requireAuth } from "@/lib/middleware"; import { getGeminiService } from "@/lib/services/geminiService"; import { repositoryService } from "@/lib/services/repositoryService"; -export async function POST(request: NextRequest) { +const ANALYSIS_TIMEOUT_MS = 25_000; +const MAX_COMMITS = 10; + +const VALID_ANALYSIS_TYPES = [ + "overview", + "security", + "performance", + "quality", + "dependencies", +] as const; + +type AnalysisType = (typeof VALID_ANALYSIS_TYPES)[number]; + + + +interface LanguageEntry { + name: string; + percentage: number; +} + +interface ContributorEntry { + name: string; + commits: number; +} + +interface CommitEntry { + message: string; + author: string; + date: string | undefined; +} + +interface RepositoryContext { + languages: LanguageEntry[]; + contributors: ContributorEntry[]; + commits: CommitEntry[]; +} + +interface ResponseMeta { + duration: number; + success: boolean; +} + +interface SuccessResponse { + analysis: unknown; + type: AnalysisType; + meta: ResponseMeta; +} + +interface ErrorResponse { + error: string; + meta: ResponseMeta; +} + + +const log = { + info: (event: string, data?: Record) => + console.log(JSON.stringify({ level: "info", event, ...data, ts: new Date().toISOString() })), + + warn: (event: string, data?: Record) => + console.warn(JSON.stringify({ level: "warn", event, ...data, ts: new Date().toISOString() })), + + error: (event: string, data?: Record) => + console.error(JSON.stringify({ level: "error", event, ...data, ts: new Date().toISOString() })), +}; + +function isValidAnalysisType(value: string): value is AnalysisType { + return (VALID_ANALYSIS_TYPES as readonly string[]).includes(value); +} + +function buildContext(repository: Awaited>): RepositoryContext { + return { + languages: + repository.languages?.map((l: LanguageEntry) => ({ + name: l.name, + percentage: l.percentage, + })) ?? [], + + contributors: + repository.contributors?.map((c: ContributorEntry) => ({ + name: c.name, + commits: c.commits, + })) ?? [], + + commits: + repository.commits?.slice(0, MAX_COMMITS).map((c: { message: string; authorName: string; committedAt?: Date }) => ({ + message: c.message, + author: c.authorName, + date: c.committedAt?.toISOString(), + })) ?? [], + }; +} + +export async function POST(request: NextRequest): Promise> { const startTime = Date.now(); + const noStoreHeaders = { "Cache-Control": "no-store" }; + + const duration = () => Date.now() - startTime; + + const errorResponse = ( + message: string, + status: number, + extra?: Record + ): NextResponse => + NextResponse.json( + { error: message, meta: { duration: duration(), success: false }, ...extra }, + { status, headers: noStoreHeaders } + ); try { const user = await requireAuth(request); const body = await request.json(); - const { repositoryId, type } = body; - - console.log("[RunAnalysis] Started", { - userId: user.userId, - repositoryId, - type, - }); - - if (!repositoryId || !type) { - return NextResponse.json( - { error: "Repository ID and analysis type are required" }, - { status: 400 } - ); + const { repositoryId, type } = body as { repositoryId: unknown; type: unknown }; + + if (typeof repositoryId !== "string" || repositoryId.trim() === "") { + log.warn("validation_failed", { reason: "invalid_repository_id", userId: user.userId }); + return errorResponse("Invalid repository ID", 400); } - const repository = await repositoryService.getRepository( - repositoryId, - user.userId - ); + if (typeof type !== "string" || type.trim() === "") { + log.warn("validation_failed", { reason: "missing_analysis_type", userId: user.userId }); + return errorResponse("Analysis type is required", 400); + } - if (!repository) { - return NextResponse.json( - { error: "Repository not found" }, - { status: 404 } + if (!isValidAnalysisType(type)) { + log.warn("validation_failed", { + reason: "unknown_analysis_type", + provided: type, + valid: VALID_ANALYSIS_TYPES, + userId: user.userId, + }); + return errorResponse( + `Invalid analysis type. Must be one of: ${VALID_ANALYSIS_TYPES.join(", ")}`, + 400 ); } - // Build lightweight context (efficiency improvement) - const context = { - languages: repository.languages?.map((l: any) => ({ - name: l.name, - percentage: l.percentage, - })) || [], - - contributors: repository.contributors?.map((c: any) => ({ - name: c.name, - commits: c.commits, - })) || [], + log.info("analysis_started", { repositoryId, type, userId: user.userId }); - commits: repository.commits?.slice(0, 10).map((c: any) => ({ - message: c.message, - author: c.authorName, - date: c.committedAt?.toISOString(), - })) || [], - }; - - console.log("[RunAnalysis] Context prepared", { - languages: context.languages.length, - contributors: context.contributors.length, - commits: context.commits.length, - }); - - // Timeout safety for Vercel (important improvement) - const analysisPromise = getGeminiService().analyzeRepository({ - repositoryId, - type, - context, - }); - - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error("Analysis timeout exceeded")), 25000) - ); + const repository = await repositoryService.getRepository(repositoryId, user.userId); - const analysis = await Promise.race([ - analysisPromise, - timeoutPromise, - ]); + if (!repository || repository.userId !== user.userId) { + log.warn("repository_not_found", { repositoryId, userId: user.userId }); + return errorResponse("Repository not found", 404); + } - const duration = Date.now() - startTime; + const context = buildContext(repository); - console.log("[RunAnalysis] Completed", { - repositoryId, - duration: `${duration}ms`, - }); + const analysisPromise = getGeminiService().analyzeRepository({ repositoryId, type, context }); + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error("Analysis timeout exceeded")), ANALYSIS_TIMEOUT_MS) + ); - return NextResponse.json({ - analysis, - type, - meta: { - duration, - success: true, - }, - }); + const analysis = await Promise.race([analysisPromise, timeoutPromise]); - } catch (error: any) { - const duration = Date.now() - startTime; + log.info("analysis_completed", { repositoryId, type, userId: user.userId, duration: duration() }); - console.error("[RunAnalysis] Failed", { - error: error?.message, - duration: `${duration}ms`, - }); + return NextResponse.json( + { analysis, type, meta: { duration: duration(), success: true } }, + { status: 200, headers: noStoreHeaders } + ); + } catch (error: unknown) { if (isHttpError(error)) { - return NextResponse.json( - { error: error.message }, - { status: error.status } - ); + const status = error.status === 403 ? 404 : error.status; + log.warn("http_error", { status, message: error.message }); + return errorResponse(error.message, status); } - return NextResponse.json( - { - error: "Failed to analyze repository", - meta: { - duration, - success: false, - }, - }, - { status: 500 } + const message = error instanceof Error ? error.message : "Unknown error"; + const isTimeout = message === "Analysis timeout exceeded"; + + log.error("unhandled_error", { message, isTimeout, duration: duration() }); + + return errorResponse( + isTimeout ? "Analysis timed out. Please try again." : "Failed to analyze repository", + isTimeout ? 504 : 500 ); } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index cb1ca341..b2b02199 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3236,12 +3236,14 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.29", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.29.tgz", "integrity": "sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==", + "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -3252,7 +3254,7 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "devOptional": true, + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" @@ -4749,6 +4751,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, "license": "MIT" }, "node_modules/d3": { @@ -10520,13 +10523,6 @@ "react": "^18.3.1" } }, - "node_modules/react-is": { - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", - "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", - "license": "MIT", - "peer": true - }, "node_modules/react-markdown": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", @@ -11147,7 +11143,6 @@ } }, "node_modules/semver": { - "version": "7.8.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", @@ -12195,7 +12190,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", From 737f43890b9a5e35f7312f13a8d42f6f4a1b395e Mon Sep 17 00:00:00 2001 From: pithva007 Date: Fri, 22 May 2026 17:39:54 +0530 Subject: [PATCH 118/123] fix(security): standardize API auth error responses --- app/analyze/route.ts | 2 +- app/api/ai/analyze-code/route.ts | 7 +- app/api/ai/analyze-repository/route.ts | 7 +- app/api/ai/chat/route.ts | 12 +--- app/api/ai/explain-file/route.ts | 17 ++--- app/api/ai/review-pr/route.ts | 2 +- app/api/ai/suggest-commit/route.ts | 7 +- app/api/analysis-jobs/[id]/route.ts | 9 +-- app/api/analysis/[jobId]/route.ts | 6 +- app/api/auth/login/route.ts | 27 ++------ app/api/auth/logout/route.ts | 11 +--- app/api/auth/me/route.ts | 11 ++-- app/api/auth/sessions/route.ts | 9 +-- app/api/auth/signup/route.ts | 7 +- .../integrations/github/app/delete/route.ts | 2 +- .../github/app/install-url/route.ts | 2 +- app/api/integrations/github/app/sync/route.ts | 2 +- app/api/integrations/github/connect/route.ts | 2 +- .../github/connected-repos/route.ts | 2 +- app/api/integrations/github/import/route.ts | 12 +--- .../integrations/github/pr-reviews/route.ts | 2 +- .../integrations/github/repositories/route.ts | 2 +- .../integrations/github/select-repos/route.ts | 2 +- app/api/internal/run-analysis/route.ts | 2 +- app/api/repositories/[id]/analyze/route.ts | 12 +--- app/api/repositories/[id]/readme/route.ts | 4 +- app/api/repositories/[id]/route.ts | 19 ++---- app/api/repositories/[id]/stats/route.ts | 9 +-- app/api/repositories/route.ts | 12 +--- app/api/users/change-password/route.ts | 14 ++-- app/api/users/me/route.ts | 16 ++--- app/api/users/profile/route.ts | 7 +- lib/api-auth.ts | 64 +++++++++++++++++++ lib/services/githubService.ts | 3 +- .../repository/RepositoryOverview.tsx | 1 - .../visualizations/CommitActivityHeatmap.tsx | 44 +++++++------ src/pages/RepositoryAnalysis.tsx | 32 +++++----- 37 files changed, 182 insertions(+), 219 deletions(-) create mode 100644 lib/api-auth.ts diff --git a/app/analyze/route.ts b/app/analyze/route.ts index 721f05b3..6b988e67 100644 --- a/app/analyze/route.ts +++ b/app/analyze/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { requireAuth } from "@/lib/middleware"; +import { requireAuth } from "@/lib/api-auth"; import prisma from "@/lib/prisma"; import { repositoryService } from "@/lib/services/repositoryService"; import { analysisJobService } from "@/lib/services/analysisJobService"; diff --git a/app/api/ai/analyze-code/route.ts b/app/api/ai/analyze-code/route.ts index dc2d9e4e..e877bfc3 100644 --- a/app/api/ai/analyze-code/route.ts +++ b/app/api/ai/analyze-code/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import { getGeminiService } from "@/lib/services/geminiService"; export async function POST(request: NextRequest) { @@ -38,9 +38,6 @@ export async function POST(request: NextRequest) { { status: error.status } ); } - return NextResponse.json( - { error: "Failed to analyze code" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/app/api/ai/analyze-repository/route.ts b/app/api/ai/analyze-repository/route.ts index 88cc06d9..a85ed685 100644 --- a/app/api/ai/analyze-repository/route.ts +++ b/app/api/ai/analyze-repository/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import { getGeminiService } from "@/lib/services/geminiService"; import { repositoryService } from "@/lib/services/repositoryService"; @@ -31,10 +31,7 @@ export async function POST(request: NextRequest) { ); if (!repository) { - return NextResponse.json( - { error: "Repository not found" }, - { status: 404 } - ); + return NextResponse.json({ error: "Not Found" }, { status: 404 }); } // Build lightweight context (efficiency improvement) diff --git a/app/api/ai/chat/route.ts b/app/api/ai/chat/route.ts index 7b2f4653..0e6975e4 100644 --- a/app/api/ai/chat/route.ts +++ b/app/api/ai/chat/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import { getGeminiService } from "@/lib/services/geminiService"; import { repositoryService } from "@/lib/services/repositoryService"; @@ -77,10 +77,7 @@ export async function POST(request: NextRequest) { ); if (!repository) { - return NextResponse.json( - { error: "Repository not found" }, - { status: 404 } - ); + return NextResponse.json({ error: "Not Found" }, { status: 404 }); } const context = { @@ -119,9 +116,6 @@ export async function POST(request: NextRequest) { ); } - return NextResponse.json( - { error: "Failed to process chat" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/app/api/ai/explain-file/route.ts b/app/api/ai/explain-file/route.ts index 4819b5b0..65686240 100644 --- a/app/api/ai/explain-file/route.ts +++ b/app/api/ai/explain-file/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import { repositoryService } from "@/lib/services/repositoryService"; type RepositoryFile = { @@ -43,19 +43,13 @@ export async function POST(request: NextRequest) { )) as Repository; if (!repository) { - return NextResponse.json( - { error: "Repository not found" }, - { status: 404 } - ); + return NextResponse.json({ error: "Not Found" }, { status: 404 }); } const file = repository.files.find((f) => f.path === filePath); if (!file) { - return NextResponse.json( - { error: "File not found in repository" }, - { status: 404 } - ); + return NextResponse.json({ error: "Not Found" }, { status: 404 }); } const explanation = `File: ${file.path}\nSize: ${file.size} bytes\nLanguage: ${file.language || "Unknown"}\n\nThis is a ${file.extension || "file"} in the repository.`; @@ -73,9 +67,6 @@ export async function POST(request: NextRequest) { { status: error.status } ); } - return NextResponse.json( - { error: "Failed to explain file" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/app/api/ai/review-pr/route.ts b/app/api/ai/review-pr/route.ts index 0a4c0a90..cb1be7c6 100644 --- a/app/api/ai/review-pr/route.ts +++ b/app/api/ai/review-pr/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { requireAuth } from "@/lib/middleware"; +import { requireAuth } from "@/lib/api-auth"; import { parsePullRequestUrl, reviewPullRequest, diff --git a/app/api/ai/suggest-commit/route.ts b/app/api/ai/suggest-commit/route.ts index aff6393e..722ccf6d 100644 --- a/app/api/ai/suggest-commit/route.ts +++ b/app/api/ai/suggest-commit/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import { getGeminiService } from "@/lib/services/geminiService"; export async function POST(request: NextRequest) { @@ -37,9 +37,6 @@ export async function POST(request: NextRequest) { { status: error.status } ); } - return NextResponse.json( - { error: "Failed to generate suggestions" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/app/api/analysis-jobs/[id]/route.ts b/app/api/analysis-jobs/[id]/route.ts index b561ebd4..a75c212c 100644 --- a/app/api/analysis-jobs/[id]/route.ts +++ b/app/api/analysis-jobs/[id]/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; -import { requireAuth, isHttpError } from "@/lib/middleware"; +import { requireAuth, isHttpError } from "@/lib/api-auth"; import { analysisJobService } from "@/lib/services/analysisJobService"; export async function GET( @@ -25,7 +25,7 @@ export async function GET( }); if (!job) { - return NextResponse.json({ error: "Job not found" }, { status: 404 }); + return NextResponse.json({ error: "Not Found" }, { status: 404 }); } const details = job.progressDetails as { retryAfter?: number; rateLimited?: boolean } | null; @@ -62,9 +62,6 @@ export async function GET( ); } - return NextResponse.json( - { error: "Failed to get analysis job" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/app/api/analysis/[jobId]/route.ts b/app/api/analysis/[jobId]/route.ts index 75fdcf8b..baad2831 100644 --- a/app/api/analysis/[jobId]/route.ts +++ b/app/api/analysis/[jobId]/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { requireAuth } from "@/lib/middleware"; +import { requireAuth } from "@/lib/api-auth"; import { analysisJobService } from "@/lib/services/analysisJobService"; export async function GET( @@ -25,12 +25,12 @@ export async function GET( const job = await analysisJobService.getJob({ jobId, userId: user.userId }); if (!job) { - return NextResponse.json({ error: "Job not found" }, { status: 404 }); + return NextResponse.json({ error: "Not Found" }, { status: 404 }); } return NextResponse.json({ job }); } catch (error: any) { console.error("GET /analysis/:jobId error:", error); - return NextResponse.json({ error: "Failed to fetch job" }, { status: 500 }); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts index a7cbb052..b3fdaa03 100644 --- a/app/api/auth/login/route.ts +++ b/app/api/auth/login/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { checkRateLimit } from '@/src/utils/rateLimit'; +import { checkRateLimit } from '@/utils/rateLimit'; import bcrypt from "bcryptjs"; import prisma from "@/lib/prisma"; import { generateToken } from "@/lib/auth"; @@ -50,10 +50,7 @@ export async function POST(request: NextRequest) { }); if (!user) { - return NextResponse.json( - { error: "Invalid email or password" }, - { status: 401 } - ); + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } // Security: never allow password login for Google-only accounts. @@ -64,29 +61,20 @@ export async function POST(request: NextRequest) { })) > 0; if (hasGoogleAccount) { - return NextResponse.json( - { error: "Email already exists. Please sign in with Google." }, - { status: 401 } - ); + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } } // Verify password const passwordHash = user.passwordHash || (user as any).password; if (!passwordHash) { - return NextResponse.json( - { error: "Invalid email or password" }, - { status: 401 } - ); + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const isValidPassword = await bcrypt.compare(password, passwordHash); if (!isValidPassword) { - return NextResponse.json( - { error: "Invalid email or password" }, - { status: 401 } - ); + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } // Generate JWT token @@ -103,9 +91,6 @@ export async function POST(request: NextRequest) { }); } catch (error) { console.error("Login error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } \ No newline at end of file diff --git a/app/api/auth/logout/route.ts b/app/api/auth/logout/route.ts index 9ce8ef4b..ebd948c9 100644 --- a/app/api/auth/logout/route.ts +++ b/app/api/auth/logout/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { getAuthUser } from "@/lib/middleware"; +import { getAuthUser } from "@/lib/api-auth"; export async function POST(request: NextRequest) { try { @@ -15,9 +15,7 @@ export async function POST(request: NextRequest) { const user = await getAuthUser(request); if (!user) { - return NextResponse.json( - { error: "Not authenticated" }, - { status: 401 }); + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const contentLength = request.headers.get("content-length"); @@ -66,9 +64,6 @@ if (contentLength && Number(contentLength) > MAX_BODY_SIZE) { console.error("Logout API Error:", error); //prevent stack trace from reaching client - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } \ No newline at end of file diff --git a/app/api/auth/me/route.ts b/app/api/auth/me/route.ts index 7cc33d7b..085c6a1a 100644 --- a/app/api/auth/me/route.ts +++ b/app/api/auth/me/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import prisma from "@/lib/prisma"; -import { getAuthUser } from "@/lib/middleware"; +import { getAuthUser } from "@/lib/api-auth"; export const dynamic = "force-dynamic"; export const runtime = "nodejs"; @@ -10,7 +10,7 @@ export async function GET(request: NextRequest) { const user = await getAuthUser(request); if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }); + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } // Fetch user details @@ -19,7 +19,7 @@ export async function GET(request: NextRequest) { }); if (!userDetails) { - return NextResponse.json({ error: "User not found" }, { status: 404 }); + return NextResponse.json({ error: "Not Found" }, { status: 404 }); } return NextResponse.json({ @@ -32,9 +32,6 @@ export async function GET(request: NextRequest) { }); } catch (error) { console.error("Get user error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/app/api/auth/sessions/route.ts b/app/api/auth/sessions/route.ts index 96c07d64..7967a460 100644 --- a/app/api/auth/sessions/route.ts +++ b/app/api/auth/sessions/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { getAuthUser } from "@/lib/middleware"; +import { getAuthUser } from "@/lib/api-auth"; import prisma from "@/lib/prisma"; import { toJsonSafe } from "@/lib/utils/jsonSafe"; @@ -10,7 +10,7 @@ export async function GET(request: NextRequest) { const user = await getAuthUser(request); if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }); + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const { searchParams } = new URL(request.url); @@ -47,9 +47,6 @@ export async function GET(request: NextRequest) { }); } catch (error: any) { console.error("Fetch sessions error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/app/api/auth/signup/route.ts b/app/api/auth/signup/route.ts index e774126d..d6f7d9d2 100644 --- a/app/api/auth/signup/route.ts +++ b/app/api/auth/signup/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { checkRateLimit } from '@/src/utils/rateLimit'; +import { checkRateLimit } from '@/utils/rateLimit'; import bcrypt from "bcryptjs"; import prisma from "@/lib/prisma"; import { generateToken } from "@/lib/auth"; @@ -105,9 +105,6 @@ export async function POST(request: NextRequest) { ); } catch (error) { console.error("Signup error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/app/api/integrations/github/app/delete/route.ts b/app/api/integrations/github/app/delete/route.ts index 583af447..031cce18 100644 --- a/app/api/integrations/github/app/delete/route.ts +++ b/app/api/integrations/github/app/delete/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import prisma from "@/lib/prisma"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import { GitHubAppService } from "@/lib/services/githubAppService"; import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; diff --git a/app/api/integrations/github/app/install-url/route.ts b/app/api/integrations/github/app/install-url/route.ts index 42cb4c39..5c5bb7f1 100644 --- a/app/api/integrations/github/app/install-url/route.ts +++ b/app/api/integrations/github/app/install-url/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; import { createSignedState } from "@/lib/utils/signedState"; diff --git a/app/api/integrations/github/app/sync/route.ts b/app/api/integrations/github/app/sync/route.ts index 5f2c57c9..b4ca97bb 100644 --- a/app/api/integrations/github/app/sync/route.ts +++ b/app/api/integrations/github/app/sync/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import prisma from "@/lib/prisma"; import { GitHubAppService } from "@/lib/services/githubAppService"; import { GitHubService, GitHubRateLimitError } from "@/lib/services/githubService"; diff --git a/app/api/integrations/github/connect/route.ts b/app/api/integrations/github/connect/route.ts index 99024d87..b277f8e1 100644 --- a/app/api/integrations/github/connect/route.ts +++ b/app/api/integrations/github/connect/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import prisma from "@/lib/prisma"; import { GitHubService, GitHubRateLimitError } from "@/lib/services/githubService"; import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; diff --git a/app/api/integrations/github/connected-repos/route.ts b/app/api/integrations/github/connected-repos/route.ts index 524a9295..2ee2e673 100644 --- a/app/api/integrations/github/connected-repos/route.ts +++ b/app/api/integrations/github/connected-repos/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import prisma from "@/lib/prisma"; import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; import { toJsonSafe } from "@/lib/utils/jsonSafe"; diff --git a/app/api/integrations/github/import/route.ts b/app/api/integrations/github/import/route.ts index b68748de..57eac147 100644 --- a/app/api/integrations/github/import/route.ts +++ b/app/api/integrations/github/import/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { requireAuth } from "@/lib/middleware"; +import { requireAuth } from "@/lib/api-auth"; import { GitHubService, GitHubRateLimitError } from "@/lib/services/githubService"; import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; import { repositoryService } from "@/lib/services/repositoryService"; @@ -36,10 +36,7 @@ export async function POST(request: NextRequest) { const repoData = await github.getRepository(parsed.owner, parsed.repo); if (!repoData) { - return NextResponse.json( - { error: "Repository not found. It may have been renamed, deleted, or is inaccessible with the provided token." }, - { status: 404 } - ); + return NextResponse.json({ error: "Not Found" }, { status: 404 }); } const repository = await repositoryService.createRepository({ @@ -60,9 +57,6 @@ export async function POST(request: NextRequest) { ); } - return NextResponse.json( - { error: "Failed to import from GitHub" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/app/api/integrations/github/pr-reviews/route.ts b/app/api/integrations/github/pr-reviews/route.ts index d56c2f7e..94acfba4 100644 --- a/app/api/integrations/github/pr-reviews/route.ts +++ b/app/api/integrations/github/pr-reviews/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import prisma from "@/lib/prisma"; import { toJsonSafe } from "@/lib/utils/jsonSafe"; diff --git a/app/api/integrations/github/repositories/route.ts b/app/api/integrations/github/repositories/route.ts index 206a7bc8..a124520e 100644 --- a/app/api/integrations/github/repositories/route.ts +++ b/app/api/integrations/github/repositories/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import { GitHubService, GitHubRateLimitError } from "@/lib/services/githubService"; import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; import prisma from "@/lib/prisma"; diff --git a/app/api/integrations/github/select-repos/route.ts b/app/api/integrations/github/select-repos/route.ts index 862505cb..3ab95ac4 100644 --- a/app/api/integrations/github/select-repos/route.ts +++ b/app/api/integrations/github/select-repos/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import prisma from "@/lib/prisma"; import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; import { toJsonSafe } from "@/lib/utils/jsonSafe"; diff --git a/app/api/internal/run-analysis/route.ts b/app/api/internal/run-analysis/route.ts index 902bc17d..70fac845 100644 --- a/app/api/internal/run-analysis/route.ts +++ b/app/api/internal/run-analysis/route.ts @@ -11,7 +11,7 @@ export async function GET(request: Request) { process.env.ANALYSIS_RUNNER_SECRET && authHeader !== `Bearer ${process.env.ANALYSIS_RUNNER_SECRET}` ) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } console.log('Starting analysis cron run...'); diff --git a/app/api/repositories/[id]/analyze/route.ts b/app/api/repositories/[id]/analyze/route.ts index e344f280..5922c2dc 100644 --- a/app/api/repositories/[id]/analyze/route.ts +++ b/app/api/repositories/[id]/analyze/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import { repositoryService } from "@/lib/services/repositoryService"; import { analysisJobService } from "@/lib/services/analysisJobService"; @@ -22,10 +22,7 @@ export async function POST( const repository = await repositoryService.getRepository(id, user.userId); if (!repository) { - return NextResponse.json( - { error: "Repository not found" }, - { status: 404 } - ); + return NextResponse.json({ error: "Not Found" }, { status: 404 }); } const job = await analysisJobService.createRepositoryAnalysisJob({ @@ -45,9 +42,6 @@ export async function POST( { status: error.status } ); } - return NextResponse.json( - { error: "Failed to start analysis" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/app/api/repositories/[id]/readme/route.ts b/app/api/repositories/[id]/readme/route.ts index 35234aa1..97ffa22d 100644 --- a/app/api/repositories/[id]/readme/route.ts +++ b/app/api/repositories/[id]/readme/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import { repositoryService } from "@/lib/services/repositoryService"; export async function POST( @@ -41,7 +41,7 @@ export async function POST( } if (error?.message === "Repository not found") { - return NextResponse.json({ error: error.message }, { status: 404 }); + return NextResponse.json({ error: "Not Found" }, { status: 404 }); } return NextResponse.json( diff --git a/app/api/repositories/[id]/route.ts b/app/api/repositories/[id]/route.ts index 10b2a656..eba7547d 100644 --- a/app/api/repositories/[id]/route.ts +++ b/app/api/repositories/[id]/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import prisma from "@/lib/prisma"; import { repositoryService } from "@/lib/services/repositoryService"; @@ -21,10 +21,7 @@ export async function GET( const repository = await repositoryService.getRepository(id, user.userId); if (!repository) { - return NextResponse.json( - { error: "Repository not found" }, - { status: 404 } - ); + return NextResponse.json({ error: "Not Found" }, { status: 404 }); } const latestJob = await prisma.analysisJob.findFirst({ @@ -58,10 +55,7 @@ export async function GET( ); } - return NextResponse.json( - { error: "Failed to get repository" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } @@ -94,12 +88,9 @@ export async function DELETE( } if (error?.message === "Repository not found") { - return NextResponse.json({ error: error.message }, { status: 404 }); + return NextResponse.json({ error: "Not Found" }, { status: 404 }); } - return NextResponse.json( - { error: "Failed to delete repository" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/app/api/repositories/[id]/stats/route.ts b/app/api/repositories/[id]/stats/route.ts index 8d21592a..695607d1 100644 --- a/app/api/repositories/[id]/stats/route.ts +++ b/app/api/repositories/[id]/stats/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import { repositoryService } from "@/lib/services/repositoryService"; export async function GET( @@ -29,11 +29,8 @@ export async function GET( ); } if (error?.message === "Repository not found") { - return NextResponse.json({ error: error.message }, { status: 404 }); + return NextResponse.json({ error: "Not Found" }, { status: 404 }); } - return NextResponse.json( - { error: "Failed to get repository statistics" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/app/api/repositories/route.ts b/app/api/repositories/route.ts index d90a3525..7b6c404c 100644 --- a/app/api/repositories/route.ts +++ b/app/api/repositories/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { isHttpError, requireAuth } from "@/lib/middleware"; +import { isHttpError, requireAuth } from "@/lib/api-auth"; import { repositoryService } from "@/lib/services/repositoryService"; import { analysisJobService } from "@/lib/services/analysisJobService"; @@ -148,10 +148,7 @@ export async function POST(request: NextRequest) { { status: error.status } ); } - return NextResponse.json( - { error: "Failed to create repository" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } @@ -169,9 +166,6 @@ export async function GET(request: NextRequest) { { status: error.status } ); } - return NextResponse.json( - { error: "Failed to list repositories" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/app/api/users/change-password/route.ts b/app/api/users/change-password/route.ts index ab0ab24d..092e52b7 100644 --- a/app/api/users/change-password/route.ts +++ b/app/api/users/change-password/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import bcrypt from "bcryptjs"; import prisma from "@/lib/prisma"; -import { requireAuth } from "@/lib/middleware"; +import { requireAuth } from "@/lib/api-auth"; export async function POST(request: NextRequest) { try { @@ -28,7 +28,7 @@ export async function POST(request: NextRequest) { }); if (!userDetails) { - return NextResponse.json({ message: "User not found" }, { status: 404 }); + return NextResponse.json({ error: "Not Found" }, { status: 404 }); } const passwordHash = @@ -47,10 +47,7 @@ export async function POST(request: NextRequest) { ); if (!isPasswordValid) { - return NextResponse.json( - { message: "Current password is incorrect" }, - { status: 401 } - ); + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } } @@ -64,9 +61,6 @@ export async function POST(request: NextRequest) { return NextResponse.json({ message: "Password changed successfully" }); } catch (error: any) { console.error("Error changing password:", error); - return NextResponse.json( - { message: "Failed to change password" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/app/api/users/me/route.ts b/app/api/users/me/route.ts index 9522db71..dbc16be7 100644 --- a/app/api/users/me/route.ts +++ b/app/api/users/me/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import prisma from "@/lib/prisma"; -import { requireAuth } from "@/lib/middleware"; +import { requireAuth } from "@/lib/api-auth"; import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; export const dynamic = "force-dynamic"; @@ -27,7 +27,7 @@ export async function GET(request: NextRequest) { })) > 0; if (!userDetails) { - return NextResponse.json({ message: "User not found" }, { status: 404 }); + return NextResponse.json({ error: "Not Found" }, { status: 404 }); } return NextResponse.json({ @@ -41,10 +41,7 @@ export async function GET(request: NextRequest) { }); } catch (error: any) { console.error("Error fetching user:", sanitizeErrorMessage(error)); - return NextResponse.json( - { message: "Failed to fetch user" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } @@ -60,11 +57,8 @@ export async function DELETE(request: NextRequest) { } catch (error: any) { console.error("Error deleting account:", sanitizeErrorMessage(error)); if (error?.code === "P2025") { - return NextResponse.json({ message: "User not found" }, { status: 404 }); + return NextResponse.json({ error: "Not Found" }, { status: 404 }); } - return NextResponse.json( - { message: "Failed to delete account" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/app/api/users/profile/route.ts b/app/api/users/profile/route.ts index a69d258a..23506bee 100644 --- a/app/api/users/profile/route.ts +++ b/app/api/users/profile/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import prisma from "@/lib/prisma"; -import { requireAuth } from "@/lib/middleware"; +import { requireAuth } from "@/lib/api-auth"; import { sanitizeErrorMessage } from "@/lib/utils/rateLimit"; import bcrypt from "bcryptjs"; @@ -98,9 +98,6 @@ export async function PUT(request: NextRequest) { }); } catch (error: any) { console.error("Error updating profile:", sanitizeErrorMessage(error)); - return NextResponse.json( - { message: "Failed to update profile" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } diff --git a/lib/api-auth.ts b/lib/api-auth.ts new file mode 100644 index 00000000..15e44fa1 --- /dev/null +++ b/lib/api-auth.ts @@ -0,0 +1,64 @@ +import { NextRequest } from "next/server"; +import { verifyToken, JWTPayload } from "./auth"; +import { getToken } from "next-auth/jwt"; + +export interface AuthenticatedRequest { + user: JWTPayload; +} + +export class HttpError extends Error { + status: number; + + constructor(status: number, message: string) { + super(message); + this.status = status; + } +} + +export function isHttpError(error: unknown): error is HttpError { + return ( + typeof error === "object" && + error !== null && + "status" in error && + typeof (error as any).status === "number" + ); +} + +export async function getAuthUser( + request: NextRequest +): Promise { + const authHeader = request.headers.get("authorization"); + + // 1) Existing JWT auth (Authorization: Bearer ...) + if (authHeader && authHeader.startsWith("Bearer ")) { + const token = authHeader.substring(7); + const payload = verifyToken(token); + if (payload) return payload; + } + + // 2) NextAuth session cookie (Google OAuth) + try { + const token = await getToken({ + req: request, + secret: process.env.NEXTAUTH_SECRET, + }); + if (!token?.sub || !token.email) return null; + + const userId = Number(token.sub); + if (!Number.isFinite(userId)) return null; + + return { userId, email: token.email }; + } catch { + return null; + } +} + +export async function requireAuth(request: NextRequest): Promise { + const user = await getAuthUser(request); + + if (!user) { + throw new HttpError(401, "Unauthorized"); + } + + return user; +} diff --git a/lib/services/githubService.ts b/lib/services/githubService.ts index 5368419e..a8a551f5 100644 --- a/lib/services/githubService.ts +++ b/lib/services/githubService.ts @@ -196,9 +196,8 @@ export class GitHubService { } } - const retryStatusCodes = [409, 502, 503, 504]; if ( - (status && retryableCodes.includes(status)) || + (status && [409, 502, 503, 504].includes(status)) || error.code === "ECONNABORTED" || error.code === "ECONNRESET" || error.code === "ETIMEDOUT" || diff --git a/src/components/repository/RepositoryOverview.tsx b/src/components/repository/RepositoryOverview.tsx index bb061d23..c842fec6 100644 --- a/src/components/repository/RepositoryOverview.tsx +++ b/src/components/repository/RepositoryOverview.tsx @@ -25,7 +25,6 @@ import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import rehypeRaw from "rehype-raw"; import rehypeSanitize, { defaultSchema } from "rehype-sanitize"; -import { EmptyState } from "@/components/ui/EmptyState"; interface RepositoryData { id: string; diff --git a/src/components/visualizations/CommitActivityHeatmap.tsx b/src/components/visualizations/CommitActivityHeatmap.tsx index 294d9910..48f63319 100644 --- a/src/components/visualizations/CommitActivityHeatmap.tsx +++ b/src/components/visualizations/CommitActivityHeatmap.tsx @@ -59,27 +59,9 @@ export function CommitActivityHeatmap({ } | null>(null); // ========================================================= - // CRITICAL FIX: EARLY EXTENDED GUARD FOR PERFORMANCE & SAFETY + // CRITICAL FIX: LATE EXTENDED GUARD FOR PERFORMANCE & SAFETY // ========================================================= - if (!repository?.commits || repository.commits.length === 0) { - return ( - -
-

Commit Activity

-

- Contribution activity over the last 52 weeks -

-
-
- -
-
- ); - } + const hasNoCommits = !repository?.commits || repository.commits.length === 0; useEffect(() => { // Advance the window automatically as time passes (refresh at next local midnight). @@ -100,7 +82,7 @@ export function CommitActivityHeatmap({ }, []); useEffect(() => { - if (!svgRef.current) return; + if (!svgRef.current || !repository?.commits || repository.commits.length === 0) return; const data = generateCommitData(repository.commits, now); const svg = d3.select(svgRef.current); @@ -369,6 +351,26 @@ export function CommitActivityHeatmap({ ? getCommitsForDate(selectedDate.date) : []; + if (hasNoCommits) { + return ( + +
+

Commit Activity

+

+ Contribution activity over the last 52 weeks +

+
+
+ +
+
+ ); + } + return (
diff --git a/src/pages/RepositoryAnalysis.tsx b/src/pages/RepositoryAnalysis.tsx index b267a454..807c145c 100644 --- a/src/pages/RepositoryAnalysis.tsx +++ b/src/pages/RepositoryAnalysis.tsx @@ -83,11 +83,11 @@ export default function RepositoryAnalysis() { const [elapsedSeconds, setElapsedSeconds] = useState(0); const pollingStartedAt = useRef(null); - // Tracks last time progress changed — prevents falsely timing out active jobs + // Tracks last time progress changed prevents falsely timing out active jobs const lastProgressAt = useRef(null); const elapsedTimer = useRef(null); - // ── Elapsed seconds ticker ──────────────────────────────────────── + // Elapsed seconds ticker useEffect(() => { if (isAnalyzing && !analysisTimedOut) { elapsedTimer.current = setInterval(() => { @@ -105,12 +105,12 @@ export default function RepositoryAnalysis() { }; }, [isAnalyzing, analysisTimedOut]); - // ── Initial fetch ───────────────────────────────────────────────── + // Initial fetch useEffect(() => { fetchRepository(); }, [id]); - // ── Job polling ─────────────────────────────────────────────────── + // Job polling useEffect(() => { const repoStatus = repository?.status as string | undefined; const jobStatus = job?.status as string | undefined; @@ -142,7 +142,7 @@ export default function RepositoryAnalysis() { const poll = async () => { if (stopped) return; - // ── Timeout guard ────────────────────────────────────────── + // Timeout guard if ( lastProgressAt.current && Date.now() - lastProgressAt.current > ANALYSIS_TIMEOUT_MS @@ -173,7 +173,7 @@ export default function RepositoryAnalysis() { // analysisTimedOut included so Check Again restarts polling }, [repository?.status, repository?.latestJob?.id, job?.id, job?.status, analysisTimedOut]); - // ── Data fetchers ───────────────────────────────────────────────── + // Data fetchers const fetchRepository = async () => { if (!id) return; try { @@ -206,7 +206,7 @@ export default function RepositoryAnalysis() { // Use functional setJob so we always compare against the latest job // state, avoiding the stale-closure bug where the polling loop holds // an old snapshot of job and never sees progress-only updates. - setJob((prevJob) => { + setJob((prevJob: any) => { const prevPercent = prevJob?.progressPercent ?? null; const prevMessage = prevJob?.progressMessage ?? null; const nextPercent = nextJob?.progressPercent ?? null; @@ -237,7 +237,7 @@ export default function RepositoryAnalysis() { } }; - // ── Delete ──────────────────────────────────────────────────────── + // Delete const handleDeleteRepository = async () => { if (!id) return; setIsDeleting(true); @@ -264,7 +264,7 @@ export default function RepositoryAnalysis() { } }; - // ── Tab content ─────────────────────────────────────────────────── + // Tab content const renderContent = () => { switch (activeTab) { case "overview": return ; @@ -277,7 +277,7 @@ export default function RepositoryAnalysis() { } }; - // ── Helpers ─────────────────────────────────────────────────────── + // Helpers const formatElapsed = (secs: number) => { if (secs < 60) return `${secs}s`; return `${Math.floor(secs / 60)}m ${secs % 60}s`; @@ -286,7 +286,7 @@ export default function RepositoryAnalysis() { const progressPercent = job?.progressPercent ?? 0; const progressMessage = job?.progressMessage || "Queued"; - // ── Render ──────────────────────────────────────────────────────── + // Render return (
@@ -323,7 +323,7 @@ export default function RepositoryAnalysis() {

{isAnalyzing && !analysisTimedOut && ( - â—‰ + Analyzing... )} @@ -338,7 +338,7 @@ export default function RepositoryAnalysis() {
- {/* ── Analyzing spinner (with timeout awareness) ── */} + {/* Analyzing spinner (with timeout awareness) */} {isAnalyzing && !analysisTimedOut ? (
@@ -378,7 +378,7 @@ export default function RepositoryAnalysis() {

Still queued after {formatElapsed(elapsedSeconds)}. - The worker runs every 5 minutes via GitHub Actions — + The worker runs every 5 minutes via GitHub Actions - it should pick this up shortly.

@@ -398,7 +398,7 @@ export default function RepositoryAnalysis() {
) : analysisTimedOut || analysisError ? ( - /* ── Timeout / error state ── */ + /* Timeout / error state */
@@ -439,7 +439,7 @@ export default function RepositoryAnalysis() {
) : ( - /* ── Done — show tabs ── */ + /* Done - show tabs */ <>
From 3e50474f36bc444fbfebd6e5aca583b5db93c471 Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Fri, 22 May 2026 17:43:35 +0530 Subject: [PATCH 119/123] fix: set repository status to failed on analysis errors --- lib/services/repositoryService.ts | 23 +++++++++++++++++++---- scripts/analysisWorker.ts | 6 ++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/services/repositoryService.ts b/lib/services/repositoryService.ts index 9e00a25a..92dbc481 100644 --- a/lib/services/repositoryService.ts +++ b/lib/services/repositoryService.ts @@ -488,10 +488,7 @@ export class RepositoryService { console.log(`Repository ${repositoryId} analysis completed`); } catch (error: any) { console.error(`Error analyzing repository ${repositoryId}:`, error); - await prisma.repository.update({ - where: { id: repositoryId }, - data: { status: "failed" }, - }); + await this.markRepositoryFailed(repositoryId, error.message); await report({ progressMessage: "Failed" }); throw error; } finally { @@ -502,6 +499,24 @@ export class RepositoryService { } } + /** + * Safely marks a repository as failed, preventing uncaught exceptions + * if the database update fails. + */ + async markRepositoryFailed(id: number, reason?: string) { + try { + await prisma.repository.update({ + where: { id }, + data: { status: "failed" }, + }); + if (reason) { + console.log(`Repository ${id} marked as failed. Reason: ${reason}`); + } + } catch (error) { + console.error(`Safeguard: Failed to update repository ${id} status to 'failed'`, error instanceof Error ? error.message : String(error)); + } + } + /** * Get repository with all related data */ diff --git a/scripts/analysisWorker.ts b/scripts/analysisWorker.ts index e105bfa7..6cbf87f5 100644 --- a/scripts/analysisWorker.ts +++ b/scripts/analysisWorker.ts @@ -124,6 +124,12 @@ async function runJob( maxAttempts: job.maxAttempts, retryAfter: retryAfter ?? undefined, }); + + const shouldRetry = job.attempts < job.maxAttempts; + if (!shouldRetry && job.type === "repository_analysis") { + await repositoryService.markRepositoryFailed(job.repositoryId, safeMessage); + } + return false; } finally { if (heartbeatTimer) clearInterval(heartbeatTimer); From 3a84342fc30ae123f809a7ce5b26ce1cdfb6f75f Mon Sep 17 00:00:00 2001 From: pithva007 Date: Fri, 22 May 2026 17:55:30 +0530 Subject: [PATCH 120/123] fix(db): prevent duplicate languages and stale cleanup --- app/api/auth/login/route.ts | 2 +- app/api/auth/signup/route.ts | 2 +- app/api/repositories/[id]/languages/route.ts | 83 +++++++++++++++++++ lib/services/githubService.ts | 3 +- lib/services/repositoryService.ts | 40 ++++++--- .../repository/RepositoryOverview.tsx | 1 - .../visualizations/CommitActivityHeatmap.tsx | 44 +++++----- src/pages/RepositoryAnalysis.tsx | 32 +++---- 8 files changed, 154 insertions(+), 53 deletions(-) create mode 100644 app/api/repositories/[id]/languages/route.ts diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts index a7cbb052..e92ca7f7 100644 --- a/app/api/auth/login/route.ts +++ b/app/api/auth/login/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { checkRateLimit } from '@/src/utils/rateLimit'; +import { checkRateLimit } from '@/utils/rateLimit'; import bcrypt from "bcryptjs"; import prisma from "@/lib/prisma"; import { generateToken } from "@/lib/auth"; diff --git a/app/api/auth/signup/route.ts b/app/api/auth/signup/route.ts index e774126d..82589b36 100644 --- a/app/api/auth/signup/route.ts +++ b/app/api/auth/signup/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { checkRateLimit } from '@/src/utils/rateLimit'; +import { checkRateLimit } from '@/utils/rateLimit'; import bcrypt from "bcryptjs"; import prisma from "@/lib/prisma"; import { generateToken } from "@/lib/auth"; diff --git a/app/api/repositories/[id]/languages/route.ts b/app/api/repositories/[id]/languages/route.ts new file mode 100644 index 00000000..4100d705 --- /dev/null +++ b/app/api/repositories/[id]/languages/route.ts @@ -0,0 +1,83 @@ +import { NextRequest, NextResponse } from "next/server"; +import { verifyToken } from "@/lib/auth"; +import prisma from "@/lib/prisma"; + +export async function POST( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const authHeader = request.headers.get("Authorization"); + if (!authHeader?.startsWith("Bearer ")) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + const token = authHeader.substring(7); + const user = verifyToken(token); + if (!user) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const repositoryId = Number(params.id); + + if (!Number.isInteger(repositoryId) || repositoryId <= 0) { + return NextResponse.json( + { error: "Invalid repository ID. Must be a positive integer." }, + { status: 400 } + ); + } + + // Verify ownership + const repository = await prisma.repository.findFirst({ + where: { id: repositoryId, userId: user.userId }, + }); + + if (!repository) { + return NextResponse.json({ error: "Repository not found" }, { status: 404 }); + } + + const body = await request.json(); + const { name, percentage, bytes, lines } = body; + + if (!name || typeof name !== "string") { + return NextResponse.json( + { error: "Language name is required" }, + { status: 400 } + ); + } + + const trimmedName = name.trim(); + if (!trimmedName) { + return NextResponse.json( + { error: "Language name cannot be empty or whitespace-only" }, + { status: 400 } + ); + } + + const language = await prisma.language.create({ + data: { + name: trimmedName, + percentage: Number(percentage) || 0, + bytes: Number(bytes) || 0, + lines: Number(lines) || 0, + repositoryId, + }, + }); + + return NextResponse.json(language, { status: 201 }); + } catch (error: any) { + console.error("Create language error:", error); + + // Handle duplicate entries explicitly + if (error?.code === "P2002") { + return NextResponse.json( + { error: "Language already exists for this repository" }, + { status: 409 } + ); + } + + return NextResponse.json( + { error: "Internal Server Error" }, + { status: 500 } + ); + } +} diff --git a/lib/services/githubService.ts b/lib/services/githubService.ts index 5368419e..a8a551f5 100644 --- a/lib/services/githubService.ts +++ b/lib/services/githubService.ts @@ -196,9 +196,8 @@ export class GitHubService { } } - const retryStatusCodes = [409, 502, 503, 504]; if ( - (status && retryableCodes.includes(status)) || + (status && [409, 502, 503, 504].includes(status)) || error.code === "ECONNABORTED" || error.code === "ECONNRESET" || error.code === "ETIMEDOUT" || diff --git a/lib/services/repositoryService.ts b/lib/services/repositoryService.ts index 9e00a25a..5d883c4c 100644 --- a/lib/services/repositoryService.ts +++ b/lib/services/repositoryService.ts @@ -459,18 +459,36 @@ export class RepositoryService { }), ); - if (languagesWithAdjustedPercentage.length > 0) { - await prisma.language.createMany({ - data: languagesWithAdjustedPercentage.map((language) => ({ - name: language.name, - percentage: language.percentage, - bytes: language.bytes, - lines: language.lines, - repositoryId, - })), - skipDuplicates: true, + await prisma.$transaction(async (tx) => { + // Always clear stale languages even when result set is empty + await tx.language.deleteMany({ + where: { repositoryId } }); - } + + if (languagesWithAdjustedPercentage.length > 0) { + const validLanguages = languagesWithAdjustedPercentage + .map((language) => { + const trimmedName = language.name.trim(); + if (!trimmedName) return null; + + return { + name: trimmedName, + percentage: language.percentage, + bytes: language.bytes, + lines: language.lines, + repositoryId, + }; + }) + .filter((lang): lang is NonNullable => lang !== null); + + if (validLanguages.length > 0) { + await tx.language.createMany({ + data: validLanguages, + skipDuplicates: true, + }); + } + } + }); // Update repository with final data await prisma.repository.update({ diff --git a/src/components/repository/RepositoryOverview.tsx b/src/components/repository/RepositoryOverview.tsx index bb061d23..c842fec6 100644 --- a/src/components/repository/RepositoryOverview.tsx +++ b/src/components/repository/RepositoryOverview.tsx @@ -25,7 +25,6 @@ import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import rehypeRaw from "rehype-raw"; import rehypeSanitize, { defaultSchema } from "rehype-sanitize"; -import { EmptyState } from "@/components/ui/EmptyState"; interface RepositoryData { id: string; diff --git a/src/components/visualizations/CommitActivityHeatmap.tsx b/src/components/visualizations/CommitActivityHeatmap.tsx index 294d9910..5d43ac4f 100644 --- a/src/components/visualizations/CommitActivityHeatmap.tsx +++ b/src/components/visualizations/CommitActivityHeatmap.tsx @@ -59,27 +59,9 @@ export function CommitActivityHeatmap({ } | null>(null); // ========================================================= - // CRITICAL FIX: EARLY EXTENDED GUARD FOR PERFORMANCE & SAFETY + // CRITICAL FIX: LATE EXTENDED GUARD FOR PERFORMANCE & SAFETY // ========================================================= - if (!repository?.commits || repository.commits.length === 0) { - return ( - -
-

Commit Activity

-

- Contribution activity over the last 52 weeks -

-
-
- -
-
- ); - } + const hasNoCommits = !repository?.commits || repository.commits.length === 0; useEffect(() => { // Advance the window automatically as time passes (refresh at next local midnight). @@ -100,7 +82,7 @@ export function CommitActivityHeatmap({ }, []); useEffect(() => { - if (!svgRef.current) return; + if (!svgRef.current || hasNoCommits) return; const data = generateCommitData(repository.commits, now); const svg = d3.select(svgRef.current); @@ -369,6 +351,26 @@ export function CommitActivityHeatmap({ ? getCommitsForDate(selectedDate.date) : []; + if (hasNoCommits) { + return ( + +
+

Commit Activity

+

+ Contribution activity over the last 52 weeks +

+
+
+ +
+
+ ); + } + return (
diff --git a/src/pages/RepositoryAnalysis.tsx b/src/pages/RepositoryAnalysis.tsx index b267a454..807c145c 100644 --- a/src/pages/RepositoryAnalysis.tsx +++ b/src/pages/RepositoryAnalysis.tsx @@ -83,11 +83,11 @@ export default function RepositoryAnalysis() { const [elapsedSeconds, setElapsedSeconds] = useState(0); const pollingStartedAt = useRef(null); - // Tracks last time progress changed — prevents falsely timing out active jobs + // Tracks last time progress changed prevents falsely timing out active jobs const lastProgressAt = useRef(null); const elapsedTimer = useRef(null); - // ── Elapsed seconds ticker ──────────────────────────────────────── + // Elapsed seconds ticker useEffect(() => { if (isAnalyzing && !analysisTimedOut) { elapsedTimer.current = setInterval(() => { @@ -105,12 +105,12 @@ export default function RepositoryAnalysis() { }; }, [isAnalyzing, analysisTimedOut]); - // ── Initial fetch ───────────────────────────────────────────────── + // Initial fetch useEffect(() => { fetchRepository(); }, [id]); - // ── Job polling ─────────────────────────────────────────────────── + // Job polling useEffect(() => { const repoStatus = repository?.status as string | undefined; const jobStatus = job?.status as string | undefined; @@ -142,7 +142,7 @@ export default function RepositoryAnalysis() { const poll = async () => { if (stopped) return; - // ── Timeout guard ────────────────────────────────────────── + // Timeout guard if ( lastProgressAt.current && Date.now() - lastProgressAt.current > ANALYSIS_TIMEOUT_MS @@ -173,7 +173,7 @@ export default function RepositoryAnalysis() { // analysisTimedOut included so Check Again restarts polling }, [repository?.status, repository?.latestJob?.id, job?.id, job?.status, analysisTimedOut]); - // ── Data fetchers ───────────────────────────────────────────────── + // Data fetchers const fetchRepository = async () => { if (!id) return; try { @@ -206,7 +206,7 @@ export default function RepositoryAnalysis() { // Use functional setJob so we always compare against the latest job // state, avoiding the stale-closure bug where the polling loop holds // an old snapshot of job and never sees progress-only updates. - setJob((prevJob) => { + setJob((prevJob: any) => { const prevPercent = prevJob?.progressPercent ?? null; const prevMessage = prevJob?.progressMessage ?? null; const nextPercent = nextJob?.progressPercent ?? null; @@ -237,7 +237,7 @@ export default function RepositoryAnalysis() { } }; - // ── Delete ──────────────────────────────────────────────────────── + // Delete const handleDeleteRepository = async () => { if (!id) return; setIsDeleting(true); @@ -264,7 +264,7 @@ export default function RepositoryAnalysis() { } }; - // ── Tab content ─────────────────────────────────────────────────── + // Tab content const renderContent = () => { switch (activeTab) { case "overview": return ; @@ -277,7 +277,7 @@ export default function RepositoryAnalysis() { } }; - // ── Helpers ─────────────────────────────────────────────────────── + // Helpers const formatElapsed = (secs: number) => { if (secs < 60) return `${secs}s`; return `${Math.floor(secs / 60)}m ${secs % 60}s`; @@ -286,7 +286,7 @@ export default function RepositoryAnalysis() { const progressPercent = job?.progressPercent ?? 0; const progressMessage = job?.progressMessage || "Queued"; - // ── Render ──────────────────────────────────────────────────────── + // Render return (
@@ -323,7 +323,7 @@ export default function RepositoryAnalysis() {

{isAnalyzing && !analysisTimedOut && ( - â—‰ + Analyzing... )} @@ -338,7 +338,7 @@ export default function RepositoryAnalysis() {
- {/* ── Analyzing spinner (with timeout awareness) ── */} + {/* Analyzing spinner (with timeout awareness) */} {isAnalyzing && !analysisTimedOut ? (
@@ -378,7 +378,7 @@ export default function RepositoryAnalysis() {

Still queued after {formatElapsed(elapsedSeconds)}. - The worker runs every 5 minutes via GitHub Actions — + The worker runs every 5 minutes via GitHub Actions - it should pick this up shortly.

@@ -398,7 +398,7 @@ export default function RepositoryAnalysis() {
) : analysisTimedOut || analysisError ? ( - /* ── Timeout / error state ── */ + /* Timeout / error state */
@@ -439,7 +439,7 @@ export default function RepositoryAnalysis() {
) : ( - /* ── Done — show tabs ── */ + /* Done - show tabs */ <>
From af202caf218d7d4e44115c79381952bdc5c3a88a Mon Sep 17 00:00:00 2001 From: Rial1608 Date: Fri, 22 May 2026 18:42:58 +0530 Subject: [PATCH 121/123] refactor: use typed error for timeout detection --- app/api/ai/analyze-repository/route.ts | 92 ++++++++++++++++++-------- lib/errors/AnalysisTimeoutError.ts | 12 ++++ lib/middleware.ts | 70 +++++++++++++++++--- 3 files changed, 138 insertions(+), 36 deletions(-) create mode 100644 lib/errors/AnalysisTimeoutError.ts diff --git a/app/api/ai/analyze-repository/route.ts b/app/api/ai/analyze-repository/route.ts index e9fd2913..99c10fe2 100644 --- a/app/api/ai/analyze-repository/route.ts +++ b/app/api/ai/analyze-repository/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import { isHttpError, requireAuth } from "@/lib/middleware"; +import { AnalysisTimeoutError } from "@/lib/errors/AnalysisTimeoutError"; import { getGeminiService } from "@/lib/services/geminiService"; import { repositoryService } from "@/lib/services/repositoryService"; @@ -16,8 +17,6 @@ const VALID_ANALYSIS_TYPES = [ type AnalysisType = (typeof VALID_ANALYSIS_TYPES)[number]; - - interface LanguageEntry { name: string; percentage: number; @@ -56,7 +55,6 @@ interface ErrorResponse { meta: ResponseMeta; } - const log = { info: (event: string, data?: Record) => console.log(JSON.stringify({ level: "info", event, ...data, ts: new Date().toISOString() })), @@ -68,11 +66,14 @@ const log = { console.error(JSON.stringify({ level: "error", event, ...data, ts: new Date().toISOString() })), }; + function isValidAnalysisType(value: string): value is AnalysisType { return (VALID_ANALYSIS_TYPES as readonly string[]).includes(value); } -function buildContext(repository: Awaited>): RepositoryContext { +function buildContext( + repository: Awaited> +): RepositoryContext { return { languages: repository.languages?.map((l: LanguageEntry) => ({ @@ -87,35 +88,51 @@ function buildContext(repository: Awaited ({ - message: c.message, - author: c.authorName, - date: c.committedAt?.toISOString(), - })) ?? [], + repository.commits + ?.slice(0, MAX_COMMITS) + .map((c: { message: string; authorName: string; committedAt?: Date }) => ({ + message: c.message, + author: c.authorName, + date: c.committedAt?.toISOString(), + })) ?? [], }; } -export async function POST(request: NextRequest): Promise> { +export async function POST( + request: NextRequest +): Promise> { const startTime = Date.now(); - const noStoreHeaders = { "Cache-Control": "no-store" }; + const noStoreHeaders = { "Cache-Control": "no-store" as const }; const duration = () => Date.now() - startTime; const errorResponse = ( message: string, - status: number, - extra?: Record + status: number ): NextResponse => NextResponse.json( - { error: message, meta: { duration: duration(), success: false }, ...extra }, + { error: message, meta: { duration: duration(), success: false } }, { status, headers: noStoreHeaders } ); try { const user = await requireAuth(request); + let repositoryId: unknown; + let type: unknown; + + try { + const body: unknown = await request.json(); - const body = await request.json(); - const { repositoryId, type } = body as { repositoryId: unknown; type: unknown }; + if (!body || typeof body !== "object" || Array.isArray(body)) { + log.warn("validation_failed", { reason: "non_object_body", userId: user.userId }); + return errorResponse("Request body must be a JSON object", 400); + } + + ({ repositoryId, type } = body as Record); + } catch { + log.warn("validation_failed", { reason: "malformed_json", userId: user.userId }); + return errorResponse("Request body is not valid JSON", 400); + } if (typeof repositoryId !== "string" || repositoryId.trim() === "") { log.warn("validation_failed", { reason: "invalid_repository_id", userId: user.userId }); @@ -142,6 +159,7 @@ export async function POST(request: NextRequest): Promise((_, reject) => - setTimeout(() => reject(new Error("Analysis timeout exceeded")), ANALYSIS_TIMEOUT_MS) - ); - - const analysis = await Promise.race([analysisPromise, timeoutPromise]); + const controller = new AbortController(); + let timeoutHandle: ReturnType | undefined; + + const timeoutPromise = new Promise((_, reject) => { + timeoutHandle = setTimeout(() => { + controller.abort(); // cancels the Gemini SDK request + reject(new AnalysisTimeoutError()); + }, ANALYSIS_TIMEOUT_MS); + }); + + let analysis: unknown; + try { + const analysisPromise = getGeminiService().analyzeRepository({ + repositoryId, + type, + context, + signal: controller.signal, // GeminiService forwards this to generateContent() + }); + analysis = await Promise.race([analysisPromise, timeoutPromise]); + } finally { + clearTimeout(timeoutHandle); // prevents timer leak on the success path + } log.info("analysis_completed", { repositoryId, type, userId: user.userId, duration: duration() }); @@ -166,14 +199,19 @@ export async function POST(request: NextRequest): Promise).name === "HttpError" && + "status" in error && + typeof (error as Record).status === "number" + ); +} export async function middleware(request: NextRequest) { try { - // Step 1: Get the logged-in user's session token const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET, }); - // Step 2: If no token, user is not logged in → redirect to login if (!token) { return NextResponse.redirect(new URL("/login", request.url)); } - const userId = token.sub; // This is the logged-in user's ID + const userId = token.sub; - // Step 3: Get the resource owner ID from the request headers (if provided) + const resourceOwnerId = request.headers.get("x-resource-owner-id"); - // Step 4: If a resource owner is specified, check it matches the logged-in user + if (resourceOwnerId && resourceOwnerId !== userId) { - // Someone is trying to access another user's data → block them! + return NextResponse.json( { error: "Forbidden: You do not have access to this resource." }, { status: 403 } ); } - // Step 5: Everything checks out → allow the request to continue return NextResponse.next(); } catch (error) { - // Step 6: Something went wrong on the server → return 500 error + console.error("Middleware error:", error); return NextResponse.json( { error: "Internal Server Error" }, @@ -42,7 +95,6 @@ export async function middleware(request: NextRequest) { } } -// This tells Next.js WHICH pages/routes to protect export const config = { matcher: ["/api/:path*", "/dashboard/:path*", "/profile/:path*"], }; \ No newline at end of file From 748de870cd217e736c61793b4c7a86fa645a7536 Mon Sep 17 00:00:00 2001 From: Anshika Rai Date: Fri, 22 May 2026 18:44:34 +0530 Subject: [PATCH 122/123] feat: improve auth provider retry and rate-limit handling --- app/api/auth/login/route.ts | 2 +- app/api/auth/signup/route.ts | 2 +- lib/auth-config.ts | 48 ++++++-------------- lib/services/githubAppService.ts | 76 ++++++++++++++++++++++---------- lib/services/githubService.ts | 2 +- pr_info.json | 0 6 files changed, 69 insertions(+), 61 deletions(-) create mode 100644 pr_info.json diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts index a7cbb052..e92ca7f7 100644 --- a/app/api/auth/login/route.ts +++ b/app/api/auth/login/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { checkRateLimit } from '@/src/utils/rateLimit'; +import { checkRateLimit } from '@/utils/rateLimit'; import bcrypt from "bcryptjs"; import prisma from "@/lib/prisma"; import { generateToken } from "@/lib/auth"; diff --git a/app/api/auth/signup/route.ts b/app/api/auth/signup/route.ts index e774126d..82589b36 100644 --- a/app/api/auth/signup/route.ts +++ b/app/api/auth/signup/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { checkRateLimit } from '@/src/utils/rateLimit'; +import { checkRateLimit } from '@/utils/rateLimit'; import bcrypt from "bcryptjs"; import prisma from "@/lib/prisma"; import { generateToken } from "@/lib/auth"; diff --git a/lib/auth-config.ts b/lib/auth-config.ts index bc90be1d..db2aa523 100644 --- a/lib/auth-config.ts +++ b/lib/auth-config.ts @@ -5,6 +5,7 @@ import prisma from "@/lib/prisma"; import bcrypt from "bcryptjs"; import dns from "dns"; import { OAuth2Client } from "google-auth-library"; +import { withRetry, sanitizeErrorMessage } from "@/lib/utils/rateLimit"; import type { Adapter, AdapterAccount, @@ -213,47 +214,27 @@ const googleTokenVerifier = isGoogleConfigured ? new OAuth2Client({ clientId: googleClientId! }) : null; -function sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -function isTransientNetworkError(error: unknown) { - const anyErr = error as any; - const code = anyErr?.code as string | undefined; - const message = (anyErr?.message as string | undefined) || ""; - - return ( - code === "ETIMEDOUT" || - code === "ECONNRESET" || - code === "EAI_AGAIN" || - code === "ENOTFOUND" || - message.includes("ETIMEDOUT") || - message.includes("ECONNRESET") - ); -} - async function verifyGoogleIdToken(idToken: string) { if (!googleTokenVerifier || !googleClientId) { throw new Error("Google OAuth is not configured"); } - // Retry once for intermittent network/cert-fetch issues. - for (let attempt = 0; attempt < 2; attempt++) { - try { - return await googleTokenVerifier.verifyIdToken({ + return withRetry( + async () => { + return await googleTokenVerifier!.verifyIdToken({ idToken, audience: googleClientId, }); - } catch (err) { - if (attempt === 0 && isTransientNetworkError(err)) { - await sleep(200); - continue; - } - throw err; + }, + { + maxRetries: 3, + onRetry: (attempt, err, delayMs) => { + console.warn( + `[auth] Google token verification failed (attempt ${attempt}), retrying in ${delayMs}ms. Error: ${sanitizeErrorMessage(err)}` + ); + }, } - } - - throw new Error("Google token verification failed"); + ); } if ((googleClientId || googleClientSecret) && !isGoogleConfigured) { @@ -482,8 +463,7 @@ export const authOptions: NextAuthOptions = { } catch (err: any) { // Avoid logging secrets/tokens. Provide enough context to diagnose. console.error("[auth] google oauth callback failed", { - message: err?.message, - code: err?.code, + message: sanitizeErrorMessage(err), providerAccountId: account?.providerAccountId, hasUserEmail: !!user?.email, }); diff --git a/lib/services/githubAppService.ts b/lib/services/githubAppService.ts index 558f7778..a3352d05 100644 --- a/lib/services/githubAppService.ts +++ b/lib/services/githubAppService.ts @@ -1,5 +1,7 @@ import axios, { isAxiosError } from "axios"; import jwt from "jsonwebtoken"; +import { withRetry, extractRetryAfter } from "@/lib/utils/rateLimit"; +import { GitHubRateLimitError } from "@/lib/services/githubService"; function getRequiredEnv(name: string): string { const value = process.env[name]; @@ -58,24 +60,37 @@ export class GitHubAppService { const appJwt = this.createAppJwt(); try { - const response = await axios.post( - `https://api.github.com/app/installations/${installationId}/access_tokens`, - {}, - { - headers: { - Authorization: `Bearer ${appJwt}`, - Accept: "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - }, + return await withRetry( + async () => { + const response = await axios.post( + `https://api.github.com/app/installations/${installationId}/access_tokens`, + {}, + { + headers: { + Authorization: `Bearer ${appJwt}`, + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }, + }, + ); + + const token = response.data?.token as string | undefined; + if (!token) { + throw new Error("Failed to obtain installation access token"); + } + return token; }, + { + maxRetries: 3, + onRetry: (attempt, _err, delayMs) => { + console.warn(`[GitHubAppService] Retrying access token fetch for installation ${installationId} (attempt ${attempt}) in ${delayMs}ms`); + } + } ); - - const token = response.data?.token as string | undefined; - if (!token) { - throw new Error("Failed to obtain installation access token"); + } catch (err: any) { + if (isAxiosError(err) && err.response?.status === 429) { + throw new GitHubRateLimitError(extractRetryAfter(err) ?? 60); } - return token; - } catch (err) { throw sanitizeAppError(err); } } @@ -87,17 +102,30 @@ export class GitHubAppService { const appJwt = this.createAppJwt(); try { - await axios.delete( - `https://api.github.com/app/installations/${installationId}`, - { - headers: { - Authorization: `Bearer ${appJwt}`, - Accept: "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - }, + await withRetry( + async () => { + await axios.delete( + `https://api.github.com/app/installations/${installationId}`, + { + headers: { + Authorization: `Bearer ${appJwt}`, + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }, + }, + ); }, + { + maxRetries: 3, + onRetry: (attempt, _err, delayMs) => { + console.warn(`[GitHubAppService] Retrying uninstall for installation ${installationId} (attempt ${attempt}) in ${delayMs}ms`); + } + } ); - } catch (err) { + } catch (err: any) { + if (isAxiosError(err) && err.response?.status === 429) { + throw new GitHubRateLimitError(extractRetryAfter(err) ?? 60); + } throw sanitizeAppError(err); } } diff --git a/lib/services/githubService.ts b/lib/services/githubService.ts index 5368419e..aec36b4f 100644 --- a/lib/services/githubService.ts +++ b/lib/services/githubService.ts @@ -198,7 +198,7 @@ export class GitHubService { const retryStatusCodes = [409, 502, 503, 504]; if ( - (status && retryableCodes.includes(status)) || + (status && retryStatusCodes.includes(status)) || error.code === "ECONNABORTED" || error.code === "ECONNRESET" || error.code === "ETIMEDOUT" || diff --git a/pr_info.json b/pr_info.json new file mode 100644 index 00000000..e69de29b From 466316cdd65360f21022a9152262a017386890d0 Mon Sep 17 00:00:00 2001 From: Rial1608 Date: Fri, 22 May 2026 19:00:23 +0530 Subject: [PATCH 123/123] fix: remove untrusted header-based ownership check from middleware --- lib/middleware.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/lib/middleware.ts b/lib/middleware.ts index 91eecb74..9d0385ce 100644 --- a/lib/middleware.ts +++ b/lib/middleware.ts @@ -71,17 +71,10 @@ export async function middleware(request: NextRequest) { const userId = token.sub; - - const resourceOwnerId = request.headers.get("x-resource-owner-id"); + // REMOVE header-based ownership check (untrusted input) - - if (resourceOwnerId && resourceOwnerId !== userId) { - - return NextResponse.json( - { error: "Forbidden: You do not have access to this resource." }, - { status: 403 } - ); - } +// Ownership validation must be handled in route handlers +// where the actual resource can be verified server-side return NextResponse.next();