diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 6347d9d7..2df57459 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,6 +1,7 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
- "bradlc.vscode-tailwindcss"
+ "bradlc.vscode-tailwindcss",
+ "prisma.prisma"
]
}
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 69cb1b8c..afb7787e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,6 +10,14 @@ RUN go mod download
COPY vendor/zoekt ./
RUN CGO_ENABLED=0 GOOS=linux go build -o /cmd/ ./cmd/...
+# ------ Build Database ------
+FROM node-alpine AS database-builder
+WORKDIR /app
+
+COPY package.json yarn.lock* ./
+COPY ./packages/db ./packages/db
+RUN yarn workspace @sourcebot/db install --frozen-lockfile
+
# ------ Build Web ------
FROM node-alpine AS web-builder
RUN apk add --no-cache libc6-compat
@@ -17,6 +25,8 @@ WORKDIR /app
COPY package.json yarn.lock* ./
COPY ./packages/web ./packages/web
+COPY --from=database-builder /app/node_modules ./node_modules
+COPY --from=database-builder /app/packages/db ./packages/db
# Fixes arm64 timeouts
RUN yarn config set registry https://registry.npmjs.org/
@@ -27,17 +37,15 @@ ENV NEXT_TELEMETRY_DISABLED=1
ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED
ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION
ENV NEXT_PUBLIC_POSTHOG_PAPIK=BAKED_NEXT_PUBLIC_POSTHOG_PAPIK
-# @note: leading "/" is required for the basePath property. @see: https://nextjs.org/docs/app/api-reference/next-config-js/basePath
-ARG NEXT_PUBLIC_DOMAIN_SUB_PATH=/BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH
-RUN yarn workspace @sourcebot/web build
-# ------ Build Database ------
-FROM node-alpine AS database-builder
-WORKDIR /app
+# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
+# causing regular expressions parsing errors when making a request. It's unclear
+# why exactly this was happening, but it's likely due to a bad replacement happening
+# in the `sed` command.
+# @note: leading "/" is required for the basePath property. @see: https://nextjs.org/docs/app/api-reference/next-config-js/basePath
+# ARG NEXT_PUBLIC_DOMAIN_SUB_PATH=/BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH
-COPY package.json yarn.lock* ./
-COPY ./packages/db ./packages/db
-RUN yarn workspace @sourcebot/db install --frozen-lockfile
+RUN yarn workspace @sourcebot/web build
# ------ Build Backend ------
diff --git a/entrypoint.sh b/entrypoint.sh
index 1a8355bc..e43c760c 100644
--- a/entrypoint.sh
+++ b/entrypoint.sh
@@ -107,46 +107,50 @@ echo -e "\e[34m[Info] Using config file at: '$CONFIG_PATH'.\e[0m"
done
}
-
-# Update specifically NEXT_PUBLIC_DOMAIN_SUB_PATH w/o requiring a rebuild.
-# Ultimately, the DOMAIN_SUB_PATH sets the `basePath` param in the next.config.mjs.
-# Similar to above, we pass in a `BAKED_` sentinal value into next.config.mjs at build
-# time. Unlike above, the `basePath` configuration is set in files other than just javascript
-# code (e.g., manifest files, css files, etc.), so this section has subtle differences.
+# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
+# causing regular expressions parsing errors when making a request. It's unclear
+# why exactly this was happening, but it's likely due to a bad replacement happening
+# in the `sed` command.
#
-# @see: https://nextjs.org/docs/app/api-reference/next-config-js/basePath
-# @see: https://phase.dev/blog/nextjs-public-runtime-variables/
-{
- if [ ! -z "$DOMAIN_SUB_PATH" ]; then
- # If the sub-path is "/", this creates problems with certain replacements. For example:
- # /BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH/_next/image -> //_next/image (notice the double slash...)
- # To get around this, we default to an empty sub-path, which is the default when no sub-path is defined.
- if [ "$DOMAIN_SUB_PATH" = "/" ]; then
- DOMAIN_SUB_PATH=""
-
- # Otherwise, we need to ensure that the sub-path starts with a slash, since this is a requirement
- # for the basePath property. For example, assume DOMAIN_SUB_PATH=/bot, then:
- # /BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH/_next/image -> /bot/_next/image
- elif [[ ! "$DOMAIN_SUB_PATH" =~ ^/ ]]; then
- DOMAIN_SUB_PATH="/$DOMAIN_SUB_PATH"
- fi
- fi
-
- if [ ! -z "$DOMAIN_SUB_PATH" ]; then
- echo -e "\e[34m[Info] DOMAIN_SUB_PATH was set to "$DOMAIN_SUB_PATH". Overriding default path.\e[0m"
- fi
-
- # Always set NEXT_PUBLIC_DOMAIN_SUB_PATH to DOMAIN_SUB_PATH (even if it is empty!!)
- export NEXT_PUBLIC_DOMAIN_SUB_PATH="$DOMAIN_SUB_PATH"
-
- # Iterate over _all_ files in the web directory, making substitutions for the `BAKED_` sentinal values
- # with their actual desired runtime value.
- find /app/packages/web -type f |
- while read file; do
- # @note: the leading "/" is required here as it is included at build time. See Dockerfile.
- sed -i "s|/BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH|${NEXT_PUBLIC_DOMAIN_SUB_PATH}|g" "$file"
- done
-}
+# # Update specifically NEXT_PUBLIC_DOMAIN_SUB_PATH w/o requiring a rebuild.
+# # Ultimately, the DOMAIN_SUB_PATH sets the `basePath` param in the next.config.mjs.
+# # Similar to above, we pass in a `BAKED_` sentinal value into next.config.mjs at build
+# # time. Unlike above, the `basePath` configuration is set in files other than just javascript
+# # code (e.g., manifest files, css files, etc.), so this section has subtle differences.
+# #
+# # @see: https://nextjs.org/docs/app/api-reference/next-config-js/basePath
+# # @see: https://phase.dev/blog/nextjs-public-runtime-variables/
+# {
+# if [ ! -z "$DOMAIN_SUB_PATH" ]; then
+# # If the sub-path is "/", this creates problems with certain replacements. For example:
+# # /BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH/_next/image -> //_next/image (notice the double slash...)
+# # To get around this, we default to an empty sub-path, which is the default when no sub-path is defined.
+# if [ "$DOMAIN_SUB_PATH" = "/" ]; then
+# DOMAIN_SUB_PATH=""
+
+# # Otherwise, we need to ensure that the sub-path starts with a slash, since this is a requirement
+# # for the basePath property. For example, assume DOMAIN_SUB_PATH=/bot, then:
+# # /BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH/_next/image -> /bot/_next/image
+# elif [[ ! "$DOMAIN_SUB_PATH" =~ ^/ ]]; then
+# DOMAIN_SUB_PATH="/$DOMAIN_SUB_PATH"
+# fi
+# fi
+
+# if [ ! -z "$DOMAIN_SUB_PATH" ]; then
+# echo -e "\e[34m[Info] DOMAIN_SUB_PATH was set to "$DOMAIN_SUB_PATH". Overriding default path.\e[0m"
+# fi
+
+# # Always set NEXT_PUBLIC_DOMAIN_SUB_PATH to DOMAIN_SUB_PATH (even if it is empty!!)
+# export NEXT_PUBLIC_DOMAIN_SUB_PATH="$DOMAIN_SUB_PATH"
+
+# # Iterate over _all_ files in the web directory, making substitutions for the `BAKED_` sentinal values
+# # with their actual desired runtime value.
+# find /app/packages/web -type f |
+# while read file; do
+# # @note: the leading "/" is required here as it is included at build time. See Dockerfile.
+# sed -i "s|/BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH|${NEXT_PUBLIC_DOMAIN_SUB_PATH}|g" "$file"
+# done
+# }
# Run supervisord
diff --git a/package.json b/package.json
index 4062ebdd..786cf2da 100644
--- a/package.json
+++ b/package.json
@@ -6,8 +6,8 @@
"scripts": {
"build": "yarn workspaces run build",
"test": "yarn workspaces run test",
- "dev": "npm-run-all --print-label --parallel dev:zoekt dev:backend dev:web dev:redis",
- "dev:mt": "npm-run-all --print-label --parallel dev:zoekt:mt dev:backend dev:web dev:redis",
+ "dev": "yarn workspace @sourcebot/db prisma:migrate:dev && npm-run-all --print-label --parallel dev:zoekt dev:backend dev:web dev:redis",
+ "dev:mt": "yarn workspace @sourcebot/db prisma:migrate:dev && npm-run-all --print-label --parallel dev:zoekt:mt dev:backend dev:web dev:redis",
"dev:zoekt": "export PATH=\"$PWD/bin:$PATH\" && export SRC_TENANT_ENFORCEMENT_MODE=none && zoekt-webserver -index .sourcebot/index -rpc",
"dev:zoekt:mt": "export PATH=\"$PWD/bin:$PATH\" && export SRC_TENANT_ENFORCEMENT_MODE=strict && zoekt-webserver -index .sourcebot/index -rpc",
"dev:backend": "yarn workspace @sourcebot/backend dev:watch",
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 330b5b1e..244d6223 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -32,6 +32,7 @@
"lowdb": "^7.0.1",
"micromatch": "^4.0.8",
"posthog-node": "^4.2.1",
+ "@sourcebot/db": "^0.1.0",
"simple-git": "^3.27.0",
"strip-json-comments": "^5.0.1",
"winston": "^3.15.0",
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index c81ddff4..3b416cbb 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -105,7 +105,7 @@ export const syncConfig = async (configPath: string, db: PrismaClient, signal: A
name: repoName,
tenantId: 0, // TODO: add support for tenantId in GitLab config
isFork,
- isArchived: project.archived,
+ isArchived: !!project.archived,
metadata: {
'zoekt.web-url-type': 'gitlab',
'zoekt.web-url': project.web_url,
diff --git a/packages/backend/src/gitlab.ts b/packages/backend/src/gitlab.ts
index bf8dda6b..20d4e5b1 100644
--- a/packages/backend/src/gitlab.ts
+++ b/packages/backend/src/gitlab.ts
@@ -3,7 +3,7 @@ import micromatch from "micromatch";
import { createLogger } from "./logger.js";
import { GitLabConfig } from "./schemas/v2.js";
import { AppContext } from "./types.js";
-import { getTokenFromConfig, marshalBool, measure } from "./utils.js";
+import { getTokenFromConfig, measure } from "./utils.js";
const logger = createLogger("GitLab");
export const GITLAB_CLOUD_HOSTNAME = "gitlab.com";
diff --git a/packages/db/prisma/migrations/20250115193735_auth_js_models/migration.sql b/packages/db/prisma/migrations/20250115193735_auth_js_models/migration.sql
new file mode 100644
index 00000000..a476629a
--- /dev/null
+++ b/packages/db/prisma/migrations/20250115193735_auth_js_models/migration.sql
@@ -0,0 +1,45 @@
+-- CreateTable
+CREATE TABLE "User" (
+ "id" TEXT NOT NULL PRIMARY KEY,
+ "name" TEXT,
+ "email" TEXT,
+ "emailVerified" DATETIME,
+ "image" TEXT,
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" DATETIME NOT NULL
+);
+
+-- CreateTable
+CREATE TABLE "Account" (
+ "id" TEXT NOT NULL PRIMARY KEY,
+ "userId" TEXT NOT NULL,
+ "type" TEXT NOT NULL,
+ "provider" TEXT NOT NULL,
+ "providerAccountId" TEXT NOT NULL,
+ "refresh_token" TEXT,
+ "access_token" TEXT,
+ "expires_at" INTEGER,
+ "token_type" TEXT,
+ "scope" TEXT,
+ "id_token" TEXT,
+ "session_state" TEXT,
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" DATETIME NOT NULL,
+ CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
+);
+
+-- CreateTable
+CREATE TABLE "VerificationToken" (
+ "identifier" TEXT NOT NULL,
+ "token" TEXT NOT NULL,
+ "expires" DATETIME NOT NULL
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma
index 96c97462..93b35d21 100644
--- a/packages/db/prisma/schema.prisma
+++ b/packages/db/prisma/schema.prisma
@@ -41,3 +41,48 @@ model Repo {
@@unique([external_id, external_codeHostUrl])
}
+
+// @see : https://authjs.dev/concepts/database-models#user
+model User {
+ id String @id @default(cuid())
+ name String?
+ email String? @unique
+ emailVerified DateTime?
+ image String?
+ accounts Account[]
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+}
+
+// @see : https://authjs.dev/concepts/database-models#account
+model Account {
+ id String @id @default(cuid())
+ userId String
+ type String
+ provider String
+ providerAccountId String
+ refresh_token String?
+ access_token String?
+ expires_at Int?
+ token_type String?
+ scope String?
+ id_token String?
+ session_state String?
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+ @@unique([provider, providerAccountId])
+}
+
+// @see : https://authjs.dev/concepts/database-models#verificationtoken
+model VerificationToken {
+ identifier String
+ token String
+ expires DateTime
+
+ @@unique([identifier, token])
+}
diff --git a/packages/web/next.config.mjs b/packages/web/next.config.mjs
index fe7eda84..585bb245 100644
--- a/packages/web/next.config.mjs
+++ b/packages/web/next.config.mjs
@@ -22,10 +22,14 @@ const nextConfig = {
// This is required to support PostHog trailing slash API requests
skipTrailingSlashRedirect: true,
+ // @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
+ // causing regular expressions parsing errors when making a request. It's unclear
+ // why exactly this was happening, but it's likely due to a bad replacement happening
+ // in the `sed` command.
// @note: this is evaluated at build time.
- ...(process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH ? {
- basePath: process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH,
- } : {})
+ // ...(process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH ? {
+ // basePath: process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH,
+ // } : {})
};
export default nextConfig;
diff --git a/packages/web/package.json b/packages/web/package.json
index 706b4329..b5079051 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -10,6 +10,7 @@
"test": "vitest"
},
"dependencies": {
+ "@auth/prisma-adapter": "^2.7.4",
"@codemirror/commands": "^6.6.0",
"@codemirror/lang-cpp": "^6.0.2",
"@codemirror/lang-css": "^6.3.0",
@@ -39,6 +40,7 @@
"@hookform/resolvers": "^3.9.0",
"@iconify/react": "^5.1.0",
"@iizukak/codemirror-lang-wgsl": "^0.3.0",
+ "@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
@@ -89,6 +91,7 @@
"http-status-codes": "^2.3.0",
"lucide-react": "^0.435.0",
"next": "14.2.21",
+ "next-auth": "^5.0.0-beta.25",
"next-themes": "^0.3.0",
"posthog-js": "^1.161.5",
"pretty-bytes": "^6.1.1",
@@ -119,9 +122,10 @@
"jsdom": "^25.0.1",
"npm-run-all": "^4.1.5",
"postcss": "^8",
+ "@sourcebot/db": "^0.1.0",
"tailwindcss": "^3.4.1",
"typescript": "^5",
"vite-tsconfig-paths": "^5.1.3",
"vitest": "^2.1.5"
}
-}
+}
\ No newline at end of file
diff --git a/packages/web/src/app/api/(server)/auth/[...nextauth]/route.ts b/packages/web/src/app/api/(server)/auth/[...nextauth]/route.ts
new file mode 100644
index 00000000..f5bc5b80
--- /dev/null
+++ b/packages/web/src/app/api/(server)/auth/[...nextauth]/route.ts
@@ -0,0 +1,2 @@
+import { handlers } from "@/auth";
+export const { GET, POST } = handlers;
\ No newline at end of file
diff --git a/packages/web/src/app/api/(server)/search/route.ts b/packages/web/src/app/api/(server)/search/route.ts
index 9ba352a4..4255d33c 100644
--- a/packages/web/src/app/api/(server)/search/route.ts
+++ b/packages/web/src/app/api/(server)/search/route.ts
@@ -8,13 +8,15 @@ import { NextRequest } from "next/server";
export const POST = async (request: NextRequest) => {
const body = await request.json();
- const tenantId = await request.headers.get("X-Tenant-ID");
+ const tenantId = request.headers.get("X-Tenant-ID");
console.log(`Search request received. Tenant ID: ${tenantId}`);
const parsed = await searchRequestSchema.safeParseAsync({
...body,
- ...(tenantId && { tenantId: parseInt(tenantId) }),
+ ...(tenantId ? {
+ tenantId: parseInt(tenantId)
+ } : {}),
});
if (!parsed.success) {
return serviceErrorResponse(
diff --git a/packages/web/src/app/components/navigationMenu.tsx b/packages/web/src/app/components/navigationMenu.tsx
index 229b7713..53c8d966 100644
--- a/packages/web/src/app/components/navigationMenu.tsx
+++ b/packages/web/src/app/components/navigationMenu.tsx
@@ -1,90 +1,112 @@
-'use client';
-
import { Button } from "@/components/ui/button";
import { NavigationMenu as NavigationMenuBase, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, navigationMenuTriggerStyle } from "@/components/ui/navigation-menu";
import Link from "next/link";
-import { GitHubLogoIcon, DiscordLogoIcon } from "@radix-ui/react-icons";
-import { SettingsDropdown } from "./settingsDropdown";
import { Separator } from "@/components/ui/separator";
import Image from "next/image";
import logoDark from "../../../public/sb_logo_dark_small.png";
import logoLight from "../../../public/sb_logo_light_small.png";
-import { useRouter } from "next/navigation";
+import { ProfilePicture } from "./profilePicture";
+import { signOut } from "@/auth";
+import { SettingsDropdown } from "./settingsDropdown";
+import { GitHubLogoIcon, DiscordLogoIcon } from "@radix-ui/react-icons";
+import { redirect } from "next/navigation";
const SOURCEBOT_DISCORD_URL = "https://discord.gg/6Fhp27x7Pb";
const SOURCEBOT_GITHUB_URL = "https://github.com/sourcebot-dev/sourcebot";
-export const NavigationMenu = () => {
- const router = useRouter();
+export const NavigationMenu = async () => {
return (
-
-
-
{
- router.push("/");
- }}
- >
-
-
-
+
+
+
+
+
+
-
-
-
-
-
- Search
-
-
-
-
-
-
- Repositories
-
-
-
-
-
-
+
+
+
+
+
+ Search
+
+
+
+
+
+
+ Repositories
+
+
+
+
+
+
-
-
+
+
)
diff --git a/packages/web/src/app/components/profilePicture.tsx b/packages/web/src/app/components/profilePicture.tsx
new file mode 100644
index 00000000..91b54d25
--- /dev/null
+++ b/packages/web/src/app/components/profilePicture.tsx
@@ -0,0 +1,20 @@
+import { auth } from "@/auth"
+import {
+ Avatar,
+ AvatarFallback,
+ AvatarImage,
+ } from "@/components/ui/avatar"
+
+export const ProfilePicture = async () => {
+ const session = await auth()
+
+ return (
+
+
+ U
+
+ )
+ }
\ No newline at end of file
diff --git a/packages/web/src/app/login/page.tsx b/packages/web/src/app/login/page.tsx
new file mode 100644
index 00000000..1fcdc795
--- /dev/null
+++ b/packages/web/src/app/login/page.tsx
@@ -0,0 +1,87 @@
+import { providerMap, signIn } from "@/auth"
+import { AuthError } from "next-auth"
+import { redirect } from "next/navigation"
+import logoDark from "@/public/sb_logo_dark_large.png";
+import logoLight from "@/public/sb_logo_light_large.png";
+import githubLogo from "@/public/github.svg";
+import Image from "next/image";
+import { Button } from "@/components/ui/button";
+
+const SIGNIN_ERROR_URL = "/login";
+
+export default async function Login(props: {
+ searchParams: { callbackUrl: string | undefined }
+}) {
+ return (
+
+
+
+
+
+
+ {
+ Object.values(providerMap)
+ .map((provider) => {
+ if (provider.id === "github") {
+ return {
+ provider,
+ logo: githubLogo,
+ }
+ }
+
+ return { provider }
+ })
+ .map(({ provider, logo }) => (
+
+ ))
+ }
+
+
+ )
+}
diff --git a/packages/web/src/app/page.tsx b/packages/web/src/app/page.tsx
index 4e90d2ab..81c19628 100644
--- a/packages/web/src/app/page.tsx
+++ b/packages/web/src/app/page.tsx
@@ -2,8 +2,8 @@ import { listRepositories } from "@/lib/server/searchService";
import { isServiceError } from "@/lib/utils";
import Image from "next/image";
import { Suspense } from "react";
-import logoDark from "../../public/sb_logo_dark_large.png";
-import logoLight from "../../public/sb_logo_light_large.png";
+import logoDark from "@/public/sb_logo_dark_large.png";
+import logoLight from "@/public/sb_logo_light_large.png";
import { NavigationMenu } from "./components/navigationMenu";
import { RepositoryCarousel } from "./components/repositoryCarousel";
import { SearchBar } from "./components/searchBar";
diff --git a/packages/web/src/auth.ts b/packages/web/src/auth.ts
new file mode 100644
index 00000000..96d6560c
--- /dev/null
+++ b/packages/web/src/auth.ts
@@ -0,0 +1,38 @@
+import NextAuth from "next-auth"
+import GitHub from "next-auth/providers/github"
+import { PrismaAdapter } from "@auth/prisma-adapter"
+import { prisma } from "@/prisma";
+import type { Provider } from "next-auth/providers"
+import { AUTH_GITHUB_CLIENT_ID, AUTH_GITHUB_CLIENT_SECRET, AUTH_SECRET } from "./lib/environment";
+
+const providers: Provider[] = [
+ GitHub({
+ clientId: AUTH_GITHUB_CLIENT_ID,
+ clientSecret: AUTH_GITHUB_CLIENT_SECRET,
+ }),
+];
+
+// @see: https://authjs.dev/guides/pages/signin
+export const providerMap = providers
+ .map((provider) => {
+ if (typeof provider === "function") {
+ const providerData = provider()
+ return { id: providerData.id, name: providerData.name }
+ } else {
+ return { id: provider.id, name: provider.name }
+ }
+ })
+ .filter((provider) => provider.id !== "credentials");
+
+
+export const { handlers, signIn, signOut, auth } = NextAuth({
+ secret: AUTH_SECRET,
+ adapter: PrismaAdapter(prisma),
+ session: {
+ strategy: "jwt",
+ },
+ providers: providers,
+ pages: {
+ signIn: "/login"
+ }
+})
diff --git a/packages/web/src/components/ui/avatar.tsx b/packages/web/src/components/ui/avatar.tsx
new file mode 100644
index 00000000..51e507ba
--- /dev/null
+++ b/packages/web/src/components/ui/avatar.tsx
@@ -0,0 +1,50 @@
+"use client"
+
+import * as React from "react"
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/lib/utils"
+
+const Avatar = React.forwardRef<
+ React.ElementRef
,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Avatar.displayName = AvatarPrimitive.Root.displayName
+
+const AvatarImage = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarImage.displayName = AvatarPrimitive.Image.displayName
+
+const AvatarFallback = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/packages/web/src/lib/environment.ts b/packages/web/src/lib/environment.ts
index 0102da6d..4725eb0d 100644
--- a/packages/web/src/lib/environment.ts
+++ b/packages/web/src/lib/environment.ts
@@ -6,3 +6,7 @@ export const ZOEKT_WEBSERVER_URL = getEnv(process.env.ZOEKT_WEBSERVER_URL, "http
export const SHARD_MAX_MATCH_COUNT = getEnvNumber(process.env.SHARD_MAX_MATCH_COUNT, 10000);
export const TOTAL_MAX_MATCH_COUNT = getEnvNumber(process.env.TOTAL_MAX_MATCH_COUNT, 100000);
export const NODE_ENV = process.env.NODE_ENV;
+
+export const AUTH_SECRET = getEnv(process.env.AUTH_SECRET); // Generate using `npx auth secret`
+export const AUTH_GITHUB_CLIENT_ID = getEnv(process.env.AUTH_GITHUB_CLIENT_ID);
+export const AUTH_GITHUB_CLIENT_SECRET = getEnv(process.env.AUTH_GITHUB_CLIENT_SECRET);
\ No newline at end of file
diff --git a/packages/web/src/lib/errorCodes.ts b/packages/web/src/lib/errorCodes.ts
index 4144a232..2f5677f5 100644
--- a/packages/web/src/lib/errorCodes.ts
+++ b/packages/web/src/lib/errorCodes.ts
@@ -5,4 +5,5 @@ export enum ErrorCode {
REPOSITORY_NOT_FOUND = 'REPOSITORY_NOT_FOUND',
FILE_NOT_FOUND = 'FILE_NOT_FOUND',
INVALID_REQUEST_BODY = 'INVALID_REQUEST_BODY',
+ NOT_AUTHENTICATED = 'NOT_AUTHENTICATED',
}
diff --git a/packages/web/src/lib/serviceError.ts b/packages/web/src/lib/serviceError.ts
index b4883913..0d97e537 100644
--- a/packages/web/src/lib/serviceError.ts
+++ b/packages/web/src/lib/serviceError.ts
@@ -67,4 +67,12 @@ export const unexpectedError = (message: string): ServiceError => {
errorCode: ErrorCode.UNEXPECTED_ERROR,
message: `Unexpected error: ${message}`,
};
+}
+
+export const notAuthenticated = (): ServiceError => {
+ return {
+ statusCode: StatusCodes.UNAUTHORIZED,
+ errorCode: ErrorCode.NOT_AUTHENTICATED,
+ message: "Not authenticated",
+ }
}
\ No newline at end of file
diff --git a/packages/web/src/middleware.ts b/packages/web/src/middleware.ts
new file mode 100644
index 00000000..febc8966
--- /dev/null
+++ b/packages/web/src/middleware.ts
@@ -0,0 +1,46 @@
+
+import { auth } from "@/auth";
+import { Session } from "next-auth";
+import { NextRequest, NextResponse } from "next/server";
+import { notAuthenticated, serviceErrorResponse } from "./lib/serviceError";
+
+interface NextAuthRequest extends NextRequest {
+ auth: Session | null;
+ }
+
+const apiMiddleware = (req: NextAuthRequest) => {
+ if (req.nextUrl.pathname.startsWith("/api/auth")) {
+ return NextResponse.next();
+ }
+
+ if (!req.auth) {
+ return serviceErrorResponse(
+ notAuthenticated(),
+ );
+ }
+
+ return NextResponse.next();
+}
+
+const defaultMiddleware = (req: NextAuthRequest) => {
+ if (!req.auth && req.nextUrl.pathname !== "/login") {
+ const newUrl = new URL("/login", req.nextUrl.origin);
+ return NextResponse.redirect(newUrl);
+ }
+
+ return NextResponse.next();
+}
+
+export default auth(async (req) => {
+ if (req.nextUrl.pathname.startsWith("/api")) {
+ return apiMiddleware(req);
+ }
+
+ return defaultMiddleware(req);
+})
+
+
+export const config = {
+ // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
+ matcher: ['/((?!_next/static|ingest|_next/image|favicon.ico|sitemap.xml|robots.txt).*)'],
+}
\ No newline at end of file
diff --git a/packages/web/src/prisma.ts b/packages/web/src/prisma.ts
new file mode 100644
index 00000000..5f5b674e
--- /dev/null
+++ b/packages/web/src/prisma.ts
@@ -0,0 +1,6 @@
+import { PrismaClient } from "@sourcebot/db";
+
+// @see: https://authjs.dev/getting-started/adapters/prisma
+const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
+export const prisma = globalForPrisma.prisma || new PrismaClient()
+if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index f84fbc20..96eb94f4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -16,6 +16,37 @@
"@types/json-schema" "^7.0.15"
js-yaml "^4.1.0"
+"@auth/core@0.37.2":
+ version "0.37.2"
+ resolved "https://registry.yarnpkg.com/@auth/core/-/core-0.37.2.tgz#0db8a94a076846bd88eb7f9273618513e2285cb2"
+ integrity sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==
+ dependencies:
+ "@panva/hkdf" "^1.2.1"
+ "@types/cookie" "0.6.0"
+ cookie "0.7.1"
+ jose "^5.9.3"
+ oauth4webapi "^3.0.0"
+ preact "10.11.3"
+ preact-render-to-string "5.2.3"
+
+"@auth/core@0.37.4":
+ version "0.37.4"
+ resolved "https://registry.yarnpkg.com/@auth/core/-/core-0.37.4.tgz#c51410aa7d0997fa22a07a196d2c21c8b1bca71b"
+ integrity sha512-HOXJwXWXQRhbBDHlMU0K/6FT1v+wjtzdKhsNg0ZN7/gne6XPsIrjZ4daMcFnbq0Z/vsAbYBinQhhua0d77v7qw==
+ dependencies:
+ "@panva/hkdf" "^1.2.1"
+ jose "^5.9.6"
+ oauth4webapi "^3.1.1"
+ preact "10.24.3"
+ preact-render-to-string "6.5.11"
+
+"@auth/prisma-adapter@^2.7.4":
+ version "2.7.4"
+ resolved "https://registry.yarnpkg.com/@auth/prisma-adapter/-/prisma-adapter-2.7.4.tgz#4890be47a9f227f449832302d955c565c02879ee"
+ integrity sha512-3T/X94R9J1sxOLQtsD3ijIZ0JGHPXlZQxRr/8NpnZBJ3KGxun/mNsZ1MwMRhTxy0mmn9JWXk7u9+xCcVn0pu3A==
+ dependencies:
+ "@auth/core" "0.37.4"
+
"@babel/runtime@^7.18.6":
version "7.25.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6"
@@ -1270,6 +1301,11 @@
dependencies:
"@octokit/openapi-types" "^22.2.0"
+"@panva/hkdf@^1.2.1":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.2.1.tgz#cb0d111ef700136f4580349ff0226bf25c853f23"
+ integrity sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==
+
"@pkgjs/parseargs@^0.11.0":
version "0.11.0"
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
@@ -1333,6 +1369,16 @@
dependencies:
"@radix-ui/react-primitive" "2.0.0"
+"@radix-ui/react-avatar@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.1.2.tgz#24af4c66bb5271460a4a6b74c4f4f9d4789d3d90"
+ integrity sha512-GaC7bXQZ5VgZvVvsJ5mu/AEbjYLnhhkoidOboC50Z6FFlLA03wG2ianUoH+zgDQ31/9gCF59bE4+2bBgTyMiig==
+ dependencies:
+ "@radix-ui/react-context" "1.1.1"
+ "@radix-ui/react-primitive" "2.0.1"
+ "@radix-ui/react-use-callback-ref" "1.1.0"
+ "@radix-ui/react-use-layout-effect" "1.1.0"
+
"@radix-ui/react-collection@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.0.tgz#f18af78e46454a2360d103c2251773028b7724ed"
@@ -1348,6 +1394,11 @@
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz#656432461fc8283d7b591dcf0d79152fae9ecc74"
integrity sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==
+"@radix-ui/react-compose-refs@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz#6f766faa975f8738269ebb8a23bad4f5a8d2faec"
+ integrity sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==
+
"@radix-ui/react-context@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8"
@@ -1503,6 +1554,13 @@
dependencies:
"@radix-ui/react-slot" "1.1.0"
+"@radix-ui/react-primitive@2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz#6d9efc550f7520135366f333d1e820cf225fad9e"
+ integrity sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==
+ dependencies:
+ "@radix-ui/react-slot" "1.1.1"
+
"@radix-ui/react-roving-focus@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz#b30c59daf7e714c748805bfe11c76f96caaac35e"
@@ -1547,6 +1605,13 @@
dependencies:
"@radix-ui/react-compose-refs" "1.1.0"
+"@radix-ui/react-slot@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.1.tgz#ab9a0ffae4027db7dc2af503c223c978706affc3"
+ integrity sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==
+ dependencies:
+ "@radix-ui/react-compose-refs" "1.1.1"
+
"@radix-ui/react-toast@^1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-toast/-/react-toast-1.2.2.tgz#fdd8ed0b80f47d6631dfd90278fee6debc06bf33"
@@ -1850,6 +1915,11 @@
resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.4.tgz#403488dc1c8d0db288270d3bbf0ce5f9c45678b4"
integrity sha512-0WR3b8eaISjEW7RpZnclONaLFDf7buaowRHdqLp4vLj54AsSAYWfh3DRbfiYJY9XDxMgx1B4sE1Afw2PGpuHOA==
+"@types/cookie@0.6.0":
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5"
+ integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==
+
"@types/estree@1.0.6", "@types/estree@^1.0.0":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
@@ -2851,6 +2921,11 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+cookie@0.7.1:
+ version "0.7.1"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9"
+ integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==
+
crelt@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72"
@@ -4338,6 +4413,11 @@ jiti@^1.21.0:
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268"
integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==
+jose@^5.9.3, jose@^5.9.6:
+ version "5.9.6"
+ resolved "https://registry.yarnpkg.com/jose/-/jose-5.9.6.tgz#77f1f901d88ebdc405e57cce08d2a91f47521883"
+ integrity sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==
+
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -4710,6 +4790,13 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
+next-auth@^5.0.0-beta.25:
+ version "5.0.0-beta.25"
+ resolved "https://registry.yarnpkg.com/next-auth/-/next-auth-5.0.0-beta.25.tgz#3a9f9734e1d8fa5ced545360f1afc24862cb92d5"
+ integrity sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==
+ dependencies:
+ "@auth/core" "0.37.2"
+
next-themes@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.3.0.tgz#b4d2a866137a67d42564b07f3a3e720e2ff3871a"
@@ -4807,6 +4894,11 @@ nwsapi@^2.2.12:
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.13.tgz#e56b4e98960e7a040e5474536587e599c4ff4655"
integrity sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==
+oauth4webapi@^3.0.0, oauth4webapi@^3.1.1:
+ version "3.1.4"
+ resolved "https://registry.yarnpkg.com/oauth4webapi/-/oauth4webapi-3.1.4.tgz#50695385cea8e7a43f3e2e23bc33ea27faece4a7"
+ integrity sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==
+
object-assign@^4.0.1, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -5143,6 +5235,28 @@ posthog-node@^4.2.1:
axios "^1.7.4"
rusha "^0.8.14"
+preact-render-to-string@5.2.3:
+ version "5.2.3"
+ resolved "https://registry.yarnpkg.com/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz#23d17376182af720b1060d5a4099843c7fe92fe4"
+ integrity sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==
+ dependencies:
+ pretty-format "^3.8.0"
+
+preact-render-to-string@6.5.11:
+ version "6.5.11"
+ resolved "https://registry.yarnpkg.com/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz#467e69908a453497bb93d4d1fc35fb749a78e027"
+ integrity sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==
+
+preact@10.11.3:
+ version "10.11.3"
+ resolved "https://registry.yarnpkg.com/preact/-/preact-10.11.3.tgz#8a7e4ba19d3992c488b0785afcc0f8aa13c78d19"
+ integrity sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==
+
+preact@10.24.3:
+ version "10.24.3"
+ resolved "https://registry.yarnpkg.com/preact/-/preact-10.24.3.tgz#086386bd47071e3b45410ef20844c21e23828f64"
+ integrity sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==
+
preact@^10.19.3:
version "10.24.2"
resolved "https://registry.yarnpkg.com/preact/-/preact-10.24.2.tgz#42179771d3b06e7adb884e3f8127ddd3d99b78f6"
@@ -5163,6 +5277,11 @@ pretty-bytes@^6.1.1:
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-6.1.1.tgz#38cd6bb46f47afbf667c202cfc754bffd2016a3b"
integrity sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==
+pretty-format@^3.8.0:
+ version "3.8.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"
+ integrity sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==
+
prisma@^6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-6.2.1.tgz#457b210326d66d0e6f583cc6f9cd2819b984408f"
@@ -5731,16 +5850,8 @@ string-argv@^0.3.1:
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
-"string-width-cjs@npm:string-width@^4.2.0":
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
- integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.1"
-
-string-width@^4.1.0:
+"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
+ name string-width-cjs
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -5837,14 +5948,7 @@ string_decoder@^1.1.1, string_decoder@^1.3.0:
dependencies:
safe-buffer "~5.2.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==