mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 03:45:06 +03:00
- X-Content-Type-Options: 'no-sniff' -> 'nosniff' in all three header
paths (security-headers.ts, next.config.ts, scripts/serve.js) — the
old value is invalid, so MIME-sniffing protection was silently off.
- Unquote Manrope in fonts.css (stylelint).
- eslint-disable the compat shim's export * (barrel-files rule) — the
shim's whole job is to mirror @sentry/react's surface.
- Extract the Monaco loader config shared by pages/_app.tsx and
routes/__root.tsx into lib/configure-monaco-loader.ts so the asset
path can't drift between the two runtimes.
- Deduplicate fileKey between useEdgeFunctionsDiff and
EdgeFunctionsDiffPanel (matching must stay behaviourally identical).
- Prettier over compat/next/router{,.test}.ts and TANSTACK_MIGRATION.md
(CI format failure).
Claude-Session: https://claude.ai/code/session_01MLH8WVg8zBJnmZTYTV3ooo
284 lines
8.9 KiB
TypeScript
284 lines
8.9 KiB
TypeScript
import { useQueries, useQueryClient } from '@tanstack/react-query'
|
|
import { useCallback, useMemo } from 'react'
|
|
|
|
import {
|
|
getEdgeFunctionBody,
|
|
type EdgeFunctionBodyData,
|
|
} from '@/data/edge-functions/edge-function-body-query'
|
|
import {
|
|
useEdgeFunctionsQuery,
|
|
type EdgeFunctionsData,
|
|
} from '@/data/edge-functions/edge-functions-query'
|
|
import { edgeFunctionsKeys } from '@/data/edge-functions/keys'
|
|
|
|
interface UseEdgeFunctionsDiffProps {
|
|
currentBranchRef?: string
|
|
mainBranchRef?: string
|
|
}
|
|
|
|
export type FileStatus = 'added' | 'removed' | 'modified' | 'unchanged'
|
|
|
|
export interface FileInfo {
|
|
key: string
|
|
status: FileStatus
|
|
}
|
|
|
|
export interface FunctionFileInfo {
|
|
[functionSlug: string]: FileInfo[]
|
|
}
|
|
|
|
export interface EdgeFunctionsDiffResult {
|
|
addedSlugs: string[]
|
|
removedSlugs: string[]
|
|
modifiedSlugs: string[]
|
|
addedBodiesMap: Record<string, EdgeFunctionBodyData | undefined>
|
|
removedBodiesMap: Record<string, EdgeFunctionBodyData | undefined>
|
|
currentBodiesMap: Record<string, EdgeFunctionBodyData | undefined>
|
|
mainBodiesMap: Record<string, EdgeFunctionBodyData | undefined>
|
|
functionFileInfo: FunctionFileInfo
|
|
isLoading: boolean
|
|
hasChanges: boolean
|
|
refetchCurrentBranchFunctions: () => void
|
|
refetchMainBranchFunctions: () => void
|
|
currentBranchFunctions?: EdgeFunctionsData
|
|
mainBranchFunctions?: EdgeFunctionsData
|
|
clearDiffsOptimistically: () => void
|
|
}
|
|
|
|
// Equivalent of path.basename without importing Node's path lib — `path` isn't
|
|
// polyfilled in the browser bundle under Vite, so importing it crashes the route.
|
|
// Exported so EdgeFunctionsDiffPanel matches files with identical semantics.
|
|
export const fileKey = (fullPath: string) => fullPath.slice(fullPath.lastIndexOf('/') + 1)
|
|
|
|
export const useEdgeFunctionsDiff = ({
|
|
currentBranchRef,
|
|
mainBranchRef,
|
|
}: UseEdgeFunctionsDiffProps): EdgeFunctionsDiffResult => {
|
|
const queryClient = useQueryClient()
|
|
|
|
// Fetch edge functions for both branches
|
|
const {
|
|
data: currentBranchFunctions,
|
|
isPending: isCurrentFunctionsLoading,
|
|
refetch: refetchCurrentBranchFunctions,
|
|
} = useEdgeFunctionsQuery(
|
|
{ projectRef: currentBranchRef },
|
|
{
|
|
enabled: !!currentBranchRef,
|
|
refetchOnMount: 'always',
|
|
staleTime: 30000, // 30 seconds
|
|
}
|
|
)
|
|
|
|
const {
|
|
data: mainBranchFunctions,
|
|
isPending: isMainFunctionsLoading,
|
|
refetch: refetchMainBranchFunctions,
|
|
} = useEdgeFunctionsQuery(
|
|
{ projectRef: mainBranchRef },
|
|
{
|
|
enabled: !!mainBranchRef,
|
|
refetchOnMount: 'always',
|
|
staleTime: 30000, // 30 seconds
|
|
}
|
|
)
|
|
|
|
// Identify added / removed / overlapping functions
|
|
const {
|
|
added = [],
|
|
removed = [],
|
|
modified = [],
|
|
} = useMemo(() => {
|
|
if (!currentBranchFunctions || !mainBranchFunctions) {
|
|
return { added: [], removed: [], modified: [] }
|
|
}
|
|
|
|
const currentFuncs = currentBranchFunctions ?? []
|
|
const mainFuncs = mainBranchFunctions ?? []
|
|
|
|
const added = currentFuncs.filter((c) => !mainFuncs.find((m) => m.slug === c.slug))
|
|
const removed = mainFuncs.filter((m) => !currentFuncs.find((c) => c.slug === m.slug))
|
|
const modified = currentFuncs.filter((c) =>
|
|
mainFuncs.find(
|
|
(m) => m.slug === c.slug && (m.ezbr_sha256 === undefined || m.ezbr_sha256 !== c.ezbr_sha256)
|
|
)
|
|
)
|
|
|
|
return { added, removed, modified }
|
|
}, [currentBranchFunctions, mainBranchFunctions])
|
|
|
|
const addedSlugs = added.map((f) => f.slug)
|
|
const removedSlugs = removed.map((f) => f.slug)
|
|
const maybeModifiedSlugs = modified.map((f) => f.slug)
|
|
|
|
// Fetch function bodies ---------------------------------------------------
|
|
const currentBodiesQueries = useQueries({
|
|
queries: maybeModifiedSlugs.map((slug) => ({
|
|
queryKey: ['edge-function-body', currentBranchRef, slug],
|
|
queryFn: ({ signal }: { signal?: AbortSignal }) =>
|
|
getEdgeFunctionBody({ projectRef: currentBranchRef, slug }, signal),
|
|
enabled: !!currentBranchRef,
|
|
refetchOnMount: 'always' as const,
|
|
})),
|
|
})
|
|
|
|
const mainBodiesQueries = useQueries({
|
|
queries: maybeModifiedSlugs.map((slug) => ({
|
|
queryKey: ['edge-function-body', mainBranchRef, slug],
|
|
queryFn: ({ signal }: { signal?: AbortSignal }) =>
|
|
getEdgeFunctionBody({ projectRef: mainBranchRef, slug }, signal),
|
|
enabled: !!mainBranchRef,
|
|
refetchOnMount: 'always' as const,
|
|
})),
|
|
})
|
|
|
|
const addedBodiesQueries = useQueries({
|
|
queries: addedSlugs.map((slug) => ({
|
|
queryKey: ['edge-function-body', currentBranchRef, slug],
|
|
queryFn: ({ signal }: { signal?: AbortSignal }) =>
|
|
getEdgeFunctionBody({ projectRef: currentBranchRef, slug }, signal),
|
|
enabled: !!currentBranchRef,
|
|
refetchOnMount: 'always' as const,
|
|
})),
|
|
})
|
|
|
|
const removedBodiesQueries = useQueries({
|
|
queries: removedSlugs.map((slug) => ({
|
|
queryKey: ['edge-function-body', mainBranchRef, slug],
|
|
queryFn: ({ signal }: { signal?: AbortSignal }) =>
|
|
getEdgeFunctionBody({ projectRef: mainBranchRef, slug }, signal),
|
|
enabled: !!mainBranchRef,
|
|
refetchOnMount: 'always' as const,
|
|
})),
|
|
})
|
|
|
|
// Flatten loading flags ----------------------------------------------------
|
|
const isLoading =
|
|
[
|
|
...currentBodiesQueries,
|
|
...mainBodiesQueries,
|
|
...addedBodiesQueries,
|
|
...removedBodiesQueries,
|
|
].some((q) => q.isLoading) ||
|
|
isCurrentFunctionsLoading ||
|
|
isMainFunctionsLoading
|
|
|
|
// Build lookup maps --------------------------------------------------------
|
|
const currentBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
|
|
currentBodiesQueries.forEach((q, idx) => {
|
|
if (q.data) currentBodiesMap[maybeModifiedSlugs[idx]] = q.data
|
|
})
|
|
|
|
const mainBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
|
|
mainBodiesQueries.forEach((q, idx) => {
|
|
if (q.data) mainBodiesMap[maybeModifiedSlugs[idx]] = q.data
|
|
})
|
|
|
|
const addedBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
|
|
addedBodiesQueries.forEach((q, idx) => {
|
|
if (q.data) addedBodiesMap[addedSlugs[idx]] = q.data
|
|
})
|
|
|
|
const removedBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
|
|
removedBodiesQueries.forEach((q, idx) => {
|
|
if (q.data) removedBodiesMap[removedSlugs[idx]] = q.data
|
|
})
|
|
|
|
// Determine modified slugs and build file info -----------------------------
|
|
const modifiedSlugs: string[] = []
|
|
const functionFileInfo: FunctionFileInfo = {}
|
|
|
|
// Process overlapping functions to determine modifications and file info
|
|
maybeModifiedSlugs.forEach((slug) => {
|
|
const currentBody = currentBodiesMap[slug]
|
|
const mainBody = mainBodiesMap[slug]
|
|
if (!currentBody || !mainBody) return
|
|
|
|
const allFileKeys = new Set(
|
|
[...currentBody.files, ...mainBody.files].map((f) => fileKey(f.name))
|
|
)
|
|
const fileInfos: FileInfo[] = []
|
|
let hasModifications = false
|
|
|
|
for (const key of allFileKeys) {
|
|
const currentFile = currentBody.files.find((f) => fileKey(f.name) === key)
|
|
const mainFile = mainBody.files.find((f) => fileKey(f.name) === key)
|
|
|
|
let status: FileStatus = 'unchanged'
|
|
|
|
if (!currentFile && mainFile) {
|
|
status = 'removed'
|
|
hasModifications = true
|
|
} else if (currentFile && !mainFile) {
|
|
status = 'added'
|
|
hasModifications = true
|
|
} else if (currentFile && mainFile && currentFile.content !== mainFile.content) {
|
|
status = 'modified'
|
|
hasModifications = true
|
|
}
|
|
|
|
fileInfos.push({ key, status })
|
|
}
|
|
|
|
if (hasModifications) {
|
|
modifiedSlugs.push(slug)
|
|
functionFileInfo[slug] = fileInfos
|
|
}
|
|
})
|
|
|
|
// Add file info for added functions
|
|
addedSlugs.forEach((slug) => {
|
|
const body = addedBodiesMap[slug]
|
|
if (body) {
|
|
functionFileInfo[slug] = body.files.map((file) => ({
|
|
key: fileKey(file.name),
|
|
status: 'added' as FileStatus,
|
|
}))
|
|
}
|
|
})
|
|
|
|
// Add file info for removed functions
|
|
removedSlugs.forEach((slug) => {
|
|
const body = removedBodiesMap[slug]
|
|
if (body) {
|
|
functionFileInfo[slug] = body.files.map((file) => ({
|
|
key: fileKey(file.name),
|
|
status: 'removed' as FileStatus,
|
|
}))
|
|
}
|
|
})
|
|
|
|
const hasChanges = addedSlugs.length > 0 || removedSlugs.length > 0 || modifiedSlugs.length > 0
|
|
|
|
const clearDiffsOptimistically = useCallback(() => {
|
|
if (!currentBranchRef || !mainBranchFunctions) return
|
|
|
|
queryClient.setQueryData(edgeFunctionsKeys.list(currentBranchRef), mainBranchFunctions)
|
|
|
|
mainBranchFunctions.forEach((func) => {
|
|
const mainBody = mainBodiesMap[func.slug]
|
|
if (mainBody) {
|
|
queryClient.setQueryData(['edge-function-body', currentBranchRef, func.slug], mainBody)
|
|
}
|
|
})
|
|
}, [currentBranchRef, mainBranchFunctions, mainBodiesMap, queryClient])
|
|
|
|
return {
|
|
addedSlugs,
|
|
removedSlugs,
|
|
modifiedSlugs,
|
|
addedBodiesMap,
|
|
removedBodiesMap,
|
|
currentBodiesMap,
|
|
mainBodiesMap,
|
|
functionFileInfo,
|
|
isLoading,
|
|
hasChanges,
|
|
refetchCurrentBranchFunctions,
|
|
refetchMainBranchFunctions,
|
|
currentBranchFunctions,
|
|
mainBranchFunctions,
|
|
clearDiffsOptimistically,
|
|
}
|
|
}
|