Skip to content

Commit d03b801

Browse files
shantanu patilclaude
authored andcommitted
Fix deploy failures: Python global annotation + conditional Clerk
API: Remove type annotation from global _client in supabase_client.py (Python 3.11 disallows annotated names declared global). Web: Make Clerk integration conditional so builds succeed without a valid publishableKey at SSG time. ConditionalClerkProvider checks for pk_ prefix before initializing. SafeSignInButton, useAuthentication, AuthButtons, and middleware all handle the Clerk-disabled case. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7c73b2a commit d03b801

8 files changed

Lines changed: 149 additions & 35 deletions

File tree

api/supabase_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def _get_client():
6060
"Install it with: pip install supabase"
6161
)
6262

63-
_client: Client = create_client(_SUPABASE_URL, _SUPABASE_KEY)
63+
_client = create_client(_SUPABASE_URL, _SUPABASE_KEY)
6464
logger.info(f"Supabase client initialised for {_SUPABASE_URL}")
6565
return _client
6666

src/app/[owner]/[repo]/page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { useWikiCache } from '@/hooks/useWikiCache';
2929
import { useRepoStructure } from '@/hooks/useRepoStructure';
3030
import { useWikiExport } from '@/hooks/useWikiExport';
3131
import { wikiStyles } from '@/styles/wikiStyles';
32-
import { SignInButton } from '@clerk/nextjs';
32+
import SafeSignInButton from '@/components/SafeSignInButton';
3333

3434
export default function RepoWikiPage() {
3535
// Get route parameters and search params
@@ -560,11 +560,11 @@ export default function RepoWikiPage() {
560560
This repository doesn&apos;t have a cached wiki yet. Sign in to generate an AI-powered wiki for <strong>{owner}/{repo}</strong>.
561561
</p>
562562
<div className="flex flex-col gap-3 items-center">
563-
<SignInButton mode="modal">
563+
<SafeSignInButton mode="modal">
564564
<button className="inline-flex items-center justify-center rounded-md text-sm font-medium bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-6 py-2 transition-colors">
565565
Sign In to Generate
566566
</button>
567-
</SignInButton>
567+
</SafeSignInButton>
568568
<button
569569
onClick={() => setIsWaitlistModalOpen(true)}
570570
className="text-sm text-muted-foreground hover:text-foreground transition-colors underline underline-offset-4"
@@ -971,11 +971,11 @@ export default function RepoWikiPage() {
971971
<p className="text-sm text-muted-foreground mb-6">
972972
Sign in to ask questions and chat with this codebase using AI.
973973
</p>
974-
<SignInButton mode="modal">
974+
<SafeSignInButton mode="modal">
975975
<button className="inline-flex items-center justify-center rounded-md text-sm font-medium bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-6 py-2 transition-colors">
976976
Sign In
977977
</button>
978-
</SignInButton>
978+
</SafeSignInButton>
979979
</div>
980980
)}
981981
</div>

src/app/layout.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Metadata } from "next";
22
import { Inter, Noto_Serif_JP, Plus_Jakarta_Sans, JetBrains_Mono } from "next/font/google";
33
import "./globals.css";
4-
import { ClerkProvider } from "@clerk/nextjs";
4+
import ConditionalClerkProvider from "@/components/ConditionalClerkProvider";
55
import { ThemeProvider } from "next-themes";
66
import { LanguageProvider } from "@/contexts/LanguageContext";
77

@@ -56,13 +56,13 @@ export default function RootLayout({
5656
>
5757
Skip to content
5858
</a>
59-
<ClerkProvider>
59+
<ConditionalClerkProvider>
6060
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
6161
<LanguageProvider>
6262
{children}
6363
</LanguageProvider>
6464
</ThemeProvider>
65-
</ClerkProvider>
65+
</ConditionalClerkProvider>
6666
</body>
6767
</html>
6868
);

src/components/AuthButtons.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,29 @@
22

33
import { SignInButton, SignedIn, SignedOut, UserButton } from "@clerk/nextjs";
44

5+
const clerkPubKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY ?? "";
6+
const isClerkEnabled = clerkPubKey.startsWith("pk_");
7+
58
interface AuthButtonsProps {
69
onWaitlistClick?: () => void;
710
}
811

912
export default function AuthButtons({ onWaitlistClick }: AuthButtonsProps) {
13+
// When Clerk is not configured, only show the waitlist button (if provided)
14+
if (!isClerkEnabled) {
15+
return onWaitlistClick ? (
16+
<div className="flex items-center gap-3">
17+
<button
18+
type="button"
19+
onClick={onWaitlistClick}
20+
className="px-4 py-2 text-label-lg text-primary-foreground bg-primary rounded-lg hover:bg-primary/90 transition-colors"
21+
>
22+
Join Waitlist
23+
</button>
24+
</div>
25+
) : null;
26+
}
27+
1028
return (
1129
<div className="flex items-center gap-3">
1230
<SignedOut>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ClerkProvider } from "@clerk/nextjs";
2+
3+
/**
4+
* Wraps children in ClerkProvider only when a valid publishable key is available.
5+
*
6+
* During Docker builds, NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY may be empty or
7+
* unavailable. The Clerk SDK validates the key eagerly and throws if it's
8+
* invalid, which breaks Next.js static page prerendering (e.g. /_not-found).
9+
*
10+
* This component checks for a key that looks valid (starts with "pk_")
11+
* before mounting ClerkProvider. When the key is missing or empty, children
12+
* render without Clerk — auth features simply won't be available.
13+
*/
14+
15+
const clerkPubKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY ?? "";
16+
const isClerkEnabled = clerkPubKey.startsWith("pk_");
17+
18+
export default function ConditionalClerkProvider({
19+
children,
20+
}: {
21+
children: React.ReactNode;
22+
}) {
23+
if (!isClerkEnabled) {
24+
return <>{children}</>;
25+
}
26+
27+
return <ClerkProvider>{children}</ClerkProvider>;
28+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"use client";
2+
3+
import React from "react";
4+
5+
const clerkPubKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY ?? "";
6+
const isClerkEnabled = clerkPubKey.startsWith("pk_");
7+
8+
/**
9+
* A wrapper around Clerk's SignInButton that gracefully degrades when
10+
* Clerk is not configured. When Clerk is unavailable, it renders
11+
* nothing (the children button is hidden since sign-in would not work).
12+
*/
13+
export default function SafeSignInButton({
14+
mode,
15+
children,
16+
}: {
17+
mode?: "modal" | "redirect";
18+
children: React.ReactNode;
19+
}) {
20+
if (!isClerkEnabled) {
21+
// Without Clerk, sign-in is not available — render nothing
22+
return null;
23+
}
24+
25+
// Lazily require to avoid context errors when ClerkProvider is absent
26+
// eslint-disable-next-line @typescript-eslint/no-require-imports
27+
const { SignInButton } = require("@clerk/nextjs");
28+
29+
return <SignInButton mode={mode}>{children}</SignInButton>;
30+
}

src/hooks/useAuthentication.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
'use client';
22

33
import { useState, useEffect, useCallback } from 'react';
4-
import { useAuth } from '@clerk/nextjs';
4+
5+
// Clerk is only available when a valid publishable key is configured.
6+
const clerkPubKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY ?? "";
7+
const isClerkEnabled = clerkPubKey.startsWith("pk_");
8+
9+
// Conditionally resolve the useAuth hook at module scope.
10+
// When Clerk is not configured, we use a no-op stub that returns safe defaults.
11+
const useAuth: () => { isLoaded: boolean; isSignedIn: boolean; getToken: (() => Promise<string | null>) | null } =
12+
isClerkEnabled
13+
// eslint-disable-next-line @typescript-eslint/no-require-imports
14+
? require('@clerk/nextjs').useAuth
15+
: () => ({ isLoaded: true, isSignedIn: false, getToken: null });
516

617
interface UseAuthenticationReturn {
718
/** Whether the backend requires auth codes (legacy) */
@@ -21,7 +32,8 @@ interface UseAuthenticationReturn {
2132
}
2233

2334
export function useAuthentication(): UseAuthenticationReturn {
24-
// Clerk auth state
35+
// useAuth is always the same function reference per build — either Clerk's
36+
// hook or the no-op stub — so this satisfies the Rules of Hooks.
2537
const { isLoaded, isSignedIn, getToken: clerkGetToken } = useAuth();
2638

2739
// Legacy backend auth status
@@ -53,7 +65,7 @@ export function useAuthentication(): UseAuthenticationReturn {
5365

5466
// Wrapper around Clerk's getToken that handles edge cases
5567
const getToken = useCallback(async (): Promise<string | null> => {
56-
if (!isLoaded || !isSignedIn) return null;
68+
if (!isLoaded || !isSignedIn || !clerkGetToken) return null;
5769
try {
5870
return await clerkGetToken();
5971
} catch (err) {

src/middleware.ts

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,34 @@
11
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
2-
import { NextResponse } from 'next/server';
2+
import { NextResponse, type NextRequest } from 'next/server';
33

44
const PLATFORMS = new Set(['github', 'gitlab', 'bitbucket']);
55

66
// Reserved paths that should NOT be treated as platform prefixes
77
const RESERVED_PATHS = new Set(['api', 'wiki', '_next', 'favicon.ico', 'embed']);
88

9+
// Check whether Clerk is configured with a valid publishable key
10+
const clerkPubKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY ?? "";
11+
const isClerkEnabled = clerkPubKey.startsWith("pk_");
12+
913
// Define public routes that don't require authentication
10-
const isPublicRoute = createRouteMatcher([
11-
"/", // Landing page
12-
"/wiki/projects", // Cached project browser
13-
"/api/(.*)", // API rewrites to backend
14-
"/webhooks/(.*)", // Webhook endpoints
15-
"/_next/(.*)", // Next.js internals
16-
"/favicon.ico", // Favicon
17-
"/.*\\..*", // Static assets (files with extensions)
18-
]);
19-
20-
export default clerkMiddleware(async (auth, request) => {
21-
// Public routes pass through without auth checks.
22-
// All other routes (e.g. /[owner]/[repo]) go through Clerk's auth
23-
// middleware which attaches auth info but does not block access.
24-
// Feature gating is handled at the component level.
25-
if (!isPublicRoute(request)) {
26-
// Don't call auth.protect() — we just let Clerk attach auth info.
27-
// The wiki viewer components will check auth status themselves.
28-
}
14+
const isPublicRoute = isClerkEnabled
15+
? createRouteMatcher([
16+
"/", // Landing page
17+
"/wiki/projects", // Cached project browser
18+
"/api/(.*)", // API rewrites to backend
19+
"/webhooks/(.*)", // Webhook endpoints
20+
"/_next/(.*)", // Next.js internals
21+
"/favicon.ico", // Favicon
22+
"/.*\\..*", // Static assets (files with extensions)
23+
])
24+
: null;
2925

30-
// Platform prefix rewriting: /github/owner/repo → /owner/repo?type=github
26+
/**
27+
* Shared platform-prefix rewrite logic.
28+
* Rewrites /github/owner/repo → /owner/repo?type=github (and likewise for gitlab/bitbucket).
29+
* Returns a NextResponse rewrite if applicable, otherwise null.
30+
*/
31+
function handlePlatformRewrite(request: NextRequest): NextResponse | null {
3132
const { pathname, searchParams } = request.nextUrl;
3233
const segments = pathname.split('/').filter(Boolean);
3334

@@ -46,8 +47,33 @@ export default clerkMiddleware(async (auth, request) => {
4647
return NextResponse.rewrite(url);
4748
}
4849

49-
return NextResponse.next();
50-
});
50+
return null;
51+
}
52+
53+
// When Clerk is enabled, wrap platform rewriting inside clerkMiddleware so
54+
// auth info is attached to every request.
55+
const clerkHandler = isClerkEnabled
56+
? clerkMiddleware(async (auth, request) => {
57+
// Public routes pass through without auth checks.
58+
// All other routes (e.g. /[owner]/[repo]) go through Clerk's auth
59+
// middleware which attaches auth info but does not block access.
60+
// Feature gating is handled at the component level.
61+
if (isPublicRoute && !isPublicRoute(request)) {
62+
// Don't call auth.protect() — we just let Clerk attach auth info.
63+
// The wiki viewer components will check auth status themselves.
64+
}
65+
66+
return handlePlatformRewrite(request) ?? NextResponse.next();
67+
})
68+
: null;
69+
70+
// When Clerk is NOT available (e.g. during Docker build or when key is not
71+
// configured), use a plain middleware that only handles platform rewrites.
72+
function plainMiddleware(request: NextRequest) {
73+
return handlePlatformRewrite(request) ?? NextResponse.next();
74+
}
75+
76+
export default isClerkEnabled ? clerkHandler! : plainMiddleware;
5177

5278
export const config = {
5379
matcher: [

0 commit comments

Comments
 (0)