| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- import { basename } from 'path'
- 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
- }
- // Small helper around path.basename but avoids importing the full Node path lib for the browser bundle
- const fileKey = (fullPath: string) => basename(fullPath)
- 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,
- }
- }
- export default useEdgeFunctionsDiff
|