fix(web): Fix loading issues with references / definitions list (#617)

This commit is contained in:
Brendan Kellam 2025-11-13 17:21:48 -08:00 committed by GitHub
parent 341836a2ed
commit fbe1073d0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 275 additions and 128 deletions

View file

@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Fixed
- Fixed spurious infinite loads with explore panel, file tree, and file search command. [#617](https://github.com/sourcebot-dev/sourcebot/pull/617)
## [4.9.2] - 2025-11-13
### Changed

View file

@ -1,10 +1,10 @@
import { getRepoInfoByName } from "@/actions";
import { PathHeader } from "@/app/[domain]/components/pathHeader";
import { Separator } from "@/components/ui/separator";
import { getFileSource } from "@/features/search/fileSourceApi";
import { cn, getCodeHostInfoForRepo, isServiceError } from "@/lib/utils";
import Image from "next/image";
import { PureCodePreviewPanel } from "./pureCodePreviewPanel";
import { getFileSource } from "@/features/search/fileSourceApi";
interface CodePreviewPanelProps {
path: string;

View file

@ -1,12 +1,12 @@
'use client';
import { useRef } from "react";
import { FileTreeItem } from "@/features/fileTree/actions";
import { FileTreeItemComponent } from "@/features/fileTree/components/fileTreeItemComponent";
import { getBrowsePath } from "../../hooks/utils";
import { ScrollArea } from "@/components/ui/scroll-area";
import { useBrowseParams } from "../../hooks/useBrowseParams";
import { useDomain } from "@/hooks/useDomain";
import { FileTreeItem } from "@/features/fileTree/types";
interface PureTreePreviewPanelProps {
items: FileTreeItem[];

View file

@ -2,7 +2,7 @@
import { Separator } from "@/components/ui/separator";
import { getRepoInfoByName } from "@/actions";
import { PathHeader } from "@/app/[domain]/components/pathHeader";
import { getFolderContents } from "@/features/fileTree/actions";
import { getFolderContents } from "@/features/fileTree/api";
import { isServiceError } from "@/lib/utils";
import { PureTreePreviewPanel } from "./pureTreePreviewPanel";

View file

@ -5,7 +5,6 @@ import { useState, useRef, useMemo, useEffect, useCallback } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { useQuery } from "@tanstack/react-query";
import { unwrapServiceError } from "@/lib/utils";
import { FileTreeItem, getFiles } from "@/features/fileTree/actions";
import { Dialog, DialogContent, DialogDescription, DialogTitle } from "@/components/ui/dialog";
import { useBrowseNavigation } from "../hooks/useBrowseNavigation";
import { useBrowseState } from "../hooks/useBrowseState";
@ -13,6 +12,8 @@ import { useBrowseParams } from "../hooks/useBrowseParams";
import { FileTreeItemIcon } from "@/features/fileTree/components/fileTreeItemIcon";
import { useLocalStorage } from "usehooks-ts";
import { Skeleton } from "@/components/ui/skeleton";
import { FileTreeItem } from "@/features/fileTree/types";
import { getFiles } from "@/app/api/(client)/client";
const MAX_RESULTS = 100;

View file

@ -55,7 +55,7 @@ export const useSuggestionsData = ({
query: `file:${suggestionQuery}`,
matches: 15,
contextLines: 1,
}, domain),
}),
select: (data): Suggestion[] => {
if (isServiceError(data)) {
return [];
@ -75,7 +75,7 @@ export const useSuggestionsData = ({
query: `sym:${suggestionQuery.length > 0 ? suggestionQuery : ".*"}`,
matches: 15,
contextLines: 1,
}, domain),
}),
select: (data): Suggestion[] => {
if (isServiceError(data)) {
return [];

View file

@ -5,8 +5,8 @@ import { CodePreview } from "./codePreview";
import { SearchResultFile } from "@/features/search/types";
import { SymbolIcon } from "@radix-ui/react-icons";
import { SetStateAction, Dispatch, useMemo } from "react";
import { getFileSource } from "@/features/search/fileSourceApi";
import { unwrapServiceError } from "@/lib/utils";
import { getFileSource } from "@/app/api/(client)/client";
interface CodePreviewPanelProps {
previewedFile: SearchResultFile;

View file

@ -66,7 +66,7 @@ export const SearchResultsPage = ({
matches: maxMatchCount,
contextLines: 3,
whole: false,
}, domain)), "client.search"),
})), "client.search"),
select: ({ data, durationMs }) => ({
...data,
totalClientSearchDurationMs: durationMs,

View file

@ -9,13 +9,22 @@ import {
SearchRequest,
SearchResponse,
} from "@/features/search/types";
import {
FindRelatedSymbolsRequest,
FindRelatedSymbolsResponse,
} from "@/features/codeNav/types";
import {
GetFilesRequest,
GetFilesResponse,
GetTreeRequest,
GetTreeResponse,
} from "@/features/fileTree/types";
export const search = async (body: SearchRequest, domain: string): Promise<SearchResponse | ServiceError> => {
export const search = async (body: SearchRequest): Promise<SearchResponse | ServiceError> => {
const result = await fetch("/api/search", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Org-Domain": domain,
},
body: JSON.stringify(body),
}).then(response => response.json());
@ -27,12 +36,11 @@ export const search = async (body: SearchRequest, domain: string): Promise<Searc
return result as SearchResponse | ServiceError;
}
export const fetchFileSource = async (body: FileSourceRequest, domain: string): Promise<FileSourceResponse | ServiceError> => {
export const getFileSource = async (body: FileSourceRequest): Promise<FileSourceResponse | ServiceError> => {
const result = await fetch("/api/source", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Org-Domain": domain,
},
body: JSON.stringify(body),
}).then(response => response.json());
@ -60,3 +68,35 @@ export const getVersion = async (): Promise<GetVersionResponse> => {
}).then(response => response.json());
return result as GetVersionResponse;
}
export const findSearchBasedSymbolReferences = async (body: FindRelatedSymbolsRequest): Promise<FindRelatedSymbolsResponse | ServiceError> => {
const result = await fetch("/api/find_references", {
method: "POST",
body: JSON.stringify(body),
}).then(response => response.json());
return result as FindRelatedSymbolsResponse | ServiceError;
}
export const findSearchBasedSymbolDefinitions = async (body: FindRelatedSymbolsRequest): Promise<FindRelatedSymbolsResponse | ServiceError> => {
const result = await fetch("/api/find_definitions", {
method: "POST",
body: JSON.stringify(body),
}).then(response => response.json());
return result as FindRelatedSymbolsResponse | ServiceError;
}
export const getTree = async (body: GetTreeRequest): Promise<GetTreeResponse | ServiceError> => {
const result = await fetch("/api/tree", {
method: "POST",
body: JSON.stringify(body),
}).then(response => response.json());
return result as GetTreeResponse | ServiceError;
}
export const getFiles = async (body: GetFilesRequest): Promise<GetFilesResponse | ServiceError> => {
const result = await fetch("/api/files", {
method: "POST",
body: JSON.stringify(body),
}).then(response => response.json());
return result as GetFilesResponse | ServiceError;
}

View file

@ -0,0 +1,23 @@
'use server';
import { getFiles } from "@/features/fileTree/api";
import { getFilesRequestSchema } from "@/features/fileTree/types";
import { schemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";
export const POST = async (request: NextRequest) => {
const body = await request.json();
const parsed = await getFilesRequestSchema.safeParseAsync(body);
if (!parsed.success) {
return serviceErrorResponse(schemaValidationError(parsed.error));
}
const response = await getFiles(parsed.data);
if (isServiceError(response)) {
return serviceErrorResponse(response);
}
return Response.json(response);
}

View file

@ -0,0 +1,22 @@
'use server';
import { findSearchBasedSymbolDefinitions } from "@/features/codeNav/api";
import { findRelatedSymbolsRequestSchema } from "@/features/codeNav/types";
import { schemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";
export const POST = async (request: NextRequest) => {
const body = await request.json();
const parsed = await findRelatedSymbolsRequestSchema.safeParseAsync(body);
if (!parsed.success) {
return serviceErrorResponse(schemaValidationError(parsed.error));
}
const response = await findSearchBasedSymbolDefinitions(parsed.data);
if (isServiceError(response)) {
return serviceErrorResponse(response);
}
return Response.json(response);
}

View file

@ -0,0 +1,20 @@
import { findSearchBasedSymbolReferences } from "@/features/codeNav/api";
import { findRelatedSymbolsRequestSchema } from "@/features/codeNav/types";
import { schemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";
export const POST = async (request: NextRequest) => {
const body = await request.json();
const parsed = await findRelatedSymbolsRequestSchema.safeParseAsync(body);
if (!parsed.success) {
return serviceErrorResponse(schemaValidationError(parsed.error));
}
const response = await findSearchBasedSymbolReferences(parsed.data);
if (isServiceError(response)) {
return serviceErrorResponse(response);
}
return Response.json(response);
}

View file

@ -0,0 +1,23 @@
'use server';
import { getTree } from "@/features/fileTree/api";
import { getTreeRequestSchema } from "@/features/fileTree/types";
import { schemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";
export const POST = async (request: NextRequest) => {
const body = await request.json();
const parsed = await getTreeRequestSchema.safeParseAsync(body);
if (!parsed.success) {
return serviceErrorResponse(schemaValidationError(parsed.error));
}
const response = await getTree(parsed.data);
if (isServiceError(response)) {
return serviceErrorResponse(response);
}
return Response.json(response);
}

View file

@ -1,11 +1,11 @@
'use client';
import { useBrowseState } from "@/app/[domain]/browse/hooks/useBrowseState";
import { findSearchBasedSymbolReferences, findSearchBasedSymbolDefinitions} from "@/app/api/(client)/client";
import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle";
import { Badge } from "@/components/ui/badge";
import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { findSearchBasedSymbolDefinitions, findSearchBasedSymbolReferences } from "@/features/codeNav/actions";
import { useDomain } from "@/hooks/useDomain";
import { unwrapServiceError } from "@/lib/utils";
import { useQuery } from "@tanstack/react-query";
@ -46,7 +46,7 @@ export const ExploreMenu = ({
symbolName: selectedSymbolInfo.symbolName,
language: selectedSymbolInfo.language,
revisionName: selectedSymbolInfo.revisionName,
}, domain)
})
),
});
@ -62,7 +62,7 @@ export const ExploreMenu = ({
symbolName: selectedSymbolInfo.symbolName,
language: selectedSymbolInfo.language,
revisionName: selectedSymbolInfo.revisionName,
}, domain)
})
),
});

View file

@ -1,4 +1,4 @@
import { findSearchBasedSymbolDefinitions } from "@/features/codeNav/actions";
import { findSearchBasedSymbolDefinitions } from "@/app/api/(client)/client";
import { SourceRange } from "@/features/search/types";
import { useDomain } from "@/hooks/useDomain";
import { unwrapServiceError } from "@/lib/utils";
@ -56,7 +56,7 @@ export const useHoveredOverSymbolInfo = ({
symbolName: symbolName!,
language,
revisionName,
}, domain)
})
),
select: ((data) => {
return data.files.flatMap((file) => {

View file

@ -251,7 +251,6 @@ const resolveFileSource = async ({ path, repo, revision }: FileSource) => {
fileName: path,
repository: repo,
branch: revision,
// @todo: handle multi-tenancy.
});
if (isServiceError(fileSource)) {

View file

@ -41,7 +41,7 @@ export const useSuggestionsData = ({
query,
matches: 10,
contextLines: 1,
}, domain))
}))
},
select: (data): FileSuggestion[] => {
return data.files.map((file) => {

View file

@ -1,6 +1,6 @@
'use client';
import { fetchFileSource } from "@/app/api/(client)/client";
import { getFileSource } from "@/app/api/(client)/client";
import { VscodeFileIcon } from "@/app/components/vscodeFileIcon";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Skeleton } from "@/components/ui/skeleton";
@ -99,11 +99,11 @@ export const ReferencedSourcesListView = ({
const fileSourceQueries = useQueries({
queries: referencedFileSources.map((file) => ({
queryKey: ['fileSource', file.path, file.repo, file.revision, domain],
queryFn: () => unwrapServiceError(fetchFileSource({
queryFn: () => unwrapServiceError(getFileSource({
fileName: file.path,
repository: file.repo,
branch: file.revision,
}, domain)),
})),
staleTime: Infinity,
})),
});

View file

@ -1,10 +1,9 @@
import { z } from "zod"
import { search } from "@/features/search/searchApi"
import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants"
import { InferToolInput, InferToolOutput, InferUITool, tool, ToolUIPart } from "ai";
import { isServiceError } from "@/lib/utils";
import { getFileSource } from "../search/fileSourceApi";
import { findSearchBasedSymbolDefinitions, findSearchBasedSymbolReferences } from "../codeNav/actions";
import { findSearchBasedSymbolDefinitions, findSearchBasedSymbolReferences } from "../codeNav/api";
import { FileSourceResponse } from "../search/types";
import { addLineNumbers, buildSearchQuery } from "./utils";
import { toolNames } from "./constants";
@ -36,8 +35,7 @@ export const findSymbolReferencesTool = tool({
symbolName: symbol,
language,
revisionName: "HEAD",
// @todo(mt): handle multi-tenancy.
}, SINGLE_TENANT_ORG_DOMAIN);
});
if (isServiceError(response)) {
return response;
@ -74,8 +72,7 @@ export const findSymbolDefinitionsTool = tool({
symbolName: symbol,
language,
revisionName: revision,
// @todo(mt): handle multi-tenancy.
}, SINGLE_TENANT_ORG_DOMAIN);
});
if (isServiceError(response)) {
return response;

View file

@ -1,27 +1,19 @@
'use server';
import 'server-only';
import { sew, withAuth, withOrgMembership } from "@/actions";
import { sew } from "@/actions";
import { searchResponseSchema } from "@/features/search/schemas";
import { search } from "@/features/search/searchApi";
import { isServiceError } from "@/lib/utils";
import { FindRelatedSymbolsResponse } from "./types";
import { ServiceError } from "@/lib/serviceError";
import { isServiceError } from "@/lib/utils";
import { withOptionalAuthV2 } from "@/withAuthV2";
import { SearchResponse } from "../search/types";
import { OrgRole } from "@sourcebot/db";
import { FindRelatedSymbolsRequest, FindRelatedSymbolsResponse } from "./types";
// The maximum number of matches to return from the search API.
const MAX_REFERENCE_COUNT = 1000;
export const findSearchBasedSymbolReferences = async (
props: {
symbolName: string,
language: string,
revisionName?: string,
},
domain: string,
): Promise<FindRelatedSymbolsResponse | ServiceError> => sew(() =>
withAuth((session) =>
withOrgMembership(session, domain, async () => {
export const findSearchBasedSymbolReferences = async (props: FindRelatedSymbolsRequest): Promise<FindRelatedSymbolsResponse | ServiceError> => sew(() =>
withOptionalAuthV2(async () => {
const {
symbolName,
language,
@ -41,20 +33,11 @@ export const findSearchBasedSymbolReferences = async (
}
return parseRelatedSymbolsSearchResponse(searchResult);
}, /* minRequiredRole = */ OrgRole.GUEST), /* allowAnonymousAccess = */ true)
);
}));
export const findSearchBasedSymbolDefinitions = async (
props: {
symbolName: string,
language: string,
revisionName?: string,
},
domain: string,
): Promise<FindRelatedSymbolsResponse | ServiceError> => sew(() =>
withAuth((session) =>
withOrgMembership(session, domain, async () => {
export const findSearchBasedSymbolDefinitions = async (props: FindRelatedSymbolsRequest): Promise<FindRelatedSymbolsResponse | ServiceError> => sew(() =>
withOptionalAuthV2(async () => {
const {
symbolName,
language,
@ -74,8 +57,7 @@ export const findSearchBasedSymbolDefinitions = async (
}
return parseRelatedSymbolsSearchResponse(searchResult);
}, /* minRequiredRole = */ OrgRole.GUEST), /* allowAnonymousAccess = */ true)
);
}));
const parseRelatedSymbolsSearchResponse = (searchResult: SearchResponse) => {
const parser = searchResponseSchema.transform(async ({ files }) => ({

View file

@ -1,20 +0,0 @@
import { rangeSchema, repositoryInfoSchema } from "../search/schemas";
import { z } from "zod";
export const findRelatedSymbolsResponseSchema = z.object({
stats: z.object({
matchCount: z.number(),
}),
files: z.array(z.object({
fileName: z.string(),
repository: z.string(),
repositoryId: z.number(),
webUrl: z.string().optional(),
language: z.string(),
matches: z.array(z.object({
lineContent: z.string(),
range: rangeSchema,
}))
})),
repositoryInfo: z.array(repositoryInfoSchema),
});

View file

@ -1,4 +1,29 @@
import { z } from "zod";
import { findRelatedSymbolsResponseSchema } from "./schemas";
import { rangeSchema, repositoryInfoSchema } from "../search/schemas";
export const findRelatedSymbolsRequestSchema = z.object({
symbolName: z.string(),
language: z.string(),
revisionName: z.string().optional(),
});
export type FindRelatedSymbolsRequest = z.infer<typeof findRelatedSymbolsRequestSchema>;
export const findRelatedSymbolsResponseSchema = z.object({
stats: z.object({
matchCount: z.number(),
}),
files: z.array(z.object({
fileName: z.string(),
repository: z.string(),
repositoryId: z.number(),
webUrl: z.string().optional(),
language: z.string(),
matches: z.array(z.object({
lineContent: z.string(),
range: rangeSchema,
}))
})),
repositoryInfo: z.array(repositoryInfoSchema),
});
export type FindRelatedSymbolsResponse = z.infer<typeof findRelatedSymbolsResponseSchema>;

View file

@ -1,4 +1,4 @@
'use server';
import 'server-only';
import { sew } from '@/actions';
import { env } from '@sourcebot/shared';
@ -8,19 +8,10 @@ import { Repo } from '@sourcebot/db';
import { createLogger } from '@sourcebot/shared';
import path from 'path';
import { simpleGit } from 'simple-git';
import { FileTreeItem, FileTreeNode } from './types';
const logger = createLogger('file-tree');
export type FileTreeItem = {
type: string;
path: string;
name: string;
}
export type FileTreeNode = FileTreeItem & {
children: FileTreeNode[];
}
/**
* Returns the tree of files (blobs) and directories (trees) for a given repository,
* at a given revision.
@ -218,7 +209,7 @@ const buildFileTree = (flatList: { type: string, path: string }[]): FileTreeNode
const part = parts[i];
const isLeaf = i === parts.length - 1;
const nodeType = isLeaf ? item.type : 'tree';
let next = current.children.find(child => child.name === part && child.type === nodeType);
let next = current.children.find((child: FileTreeNode) => child.name === part && child.type === nodeType);
if (!next) {
next = {
@ -240,7 +231,7 @@ const buildFileTree = (flatList: { type: string, path: string }[]): FileTreeNode
const sortedChildren = node.children
.map(sortTree)
.sort((a, b) => {
.sort((a: FileTreeNode, b: FileTreeNode) => {
if (a.type !== b.type) {
return a.type === 'tree' ? -1 : 1;
}

View file

@ -1,12 +1,12 @@
'use client';
import { FileTreeItem } from "../actions";
import { useEffect, useRef } from "react";
import clsx from "clsx";
import scrollIntoView from 'scroll-into-view-if-needed';
import { ChevronDownIcon, ChevronRightIcon } from "@radix-ui/react-icons";
import { FileTreeItemIcon } from "./fileTreeItemIcon";
import Link from "next/link";
import { FileTreeItem } from "../types";
export const FileTreeItemComponent = ({
node,

View file

@ -1,9 +1,9 @@
'use client';
import { FileTreeItem } from "../actions";
import { useMemo } from "react";
import { VscodeFolderIcon } from "@/app/components/vscodeFolderIcon";
import { VscodeFileIcon } from "@/app/components/vscodeFileIcon";
import { FileTreeItem } from "../types";
interface FileTreeItemIconProps {
item: FileTreeItem;

View file

@ -1,26 +1,25 @@
'use client';
import { getTree } from "../actions";
import { useQuery } from "@tanstack/react-query";
import { unwrapServiceError } from "@/lib/utils";
import { ResizablePanel } from "@/components/ui/resizable";
import { Skeleton } from "@/components/ui/skeleton";
import { useBrowseParams } from "@/app/[domain]/browse/hooks/useBrowseParams";
import { useBrowseState } from "@/app/[domain]/browse/hooks/useBrowseState";
import { PureFileTreePanel } from "./pureFileTreePanel";
import { getTree } from "@/app/api/(client)/client";
import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint";
import { Button } from "@/components/ui/button";
import { ImperativePanelHandle } from "react-resizable-panels";
import { ResizablePanel } from "@/components/ui/resizable";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { unwrapServiceError } from "@/lib/utils";
import { useQuery } from "@tanstack/react-query";
import { SearchIcon } from "lucide-react";
import { useRef } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { Separator } from "@/components/ui/separator";
import {
GoSidebarCollapse as ExpandIcon,
GoSidebarExpand as CollapseIcon
GoSidebarExpand as CollapseIcon,
GoSidebarCollapse as ExpandIcon
} from "react-icons/go";
import { Tooltip, TooltipContent } from "@/components/ui/tooltip";
import { TooltipTrigger } from "@/components/ui/tooltip";
import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint";
import { useBrowseParams } from "@/app/[domain]/browse/hooks/useBrowseParams";
import { SearchIcon } from "lucide-react";
import { ImperativePanelHandle } from "react-resizable-panels";
import { PureFileTreePanel } from "./pureFileTreePanel";
interface FileTreePanelProps {

View file

@ -1,6 +1,6 @@
'use client';
import { FileTreeNode as RawFileTreeNode } from "../actions";
import { FileTreeNode as RawFileTreeNode } from "../types";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import React, { useCallback, useMemo, useState, useEffect, useRef } from "react";
import { FileTreeItemComponent } from "./fileTreeItemComponent";

View file

@ -0,0 +1,44 @@
import { z } from "zod";
export const getTreeRequestSchema = z.object({
repoName: z.string(),
revisionName: z.string(),
});
export type GetTreeRequest = z.infer<typeof getTreeRequestSchema>;
export const getFilesRequestSchema = z.object({
repoName: z.string(),
revisionName: z.string(),
});
export type GetFilesRequest = z.infer<typeof getFilesRequestSchema>;
export const fileTreeItemSchema = z.object({
type: z.string(),
path: z.string(),
name: z.string(),
});
export type FileTreeItem = z.infer<typeof fileTreeItemSchema>;
type FileTreeNodeType = {
type: string;
path: string;
name: string;
children: FileTreeNodeType[];
};
export const fileTreeNodeSchema: z.ZodType<FileTreeNodeType> = z.lazy(() => z.object({
type: z.string(),
path: z.string(),
name: z.string(),
children: z.array(fileTreeNodeSchema),
}));
export type FileTreeNode = z.infer<typeof fileTreeNodeSchema>;
export const getTreeResponseSchema = z.object({
tree: fileTreeNodeSchema,
});
export type GetTreeResponse = z.infer<typeof getTreeResponseSchema>;
export const getFilesResponseSchema = z.array(fileTreeItemSchema);
export type GetFilesResponse = z.infer<typeof getFilesResponseSchema>;

View file

@ -1,5 +1,4 @@
'use server';
import 'server-only';
import escapeStringRegexp from "escape-string-regexp";
import { fileNotFound, ServiceError, unexpectedError } from "../../lib/serviceError";
import { FileSourceRequest, FileSourceResponse } from "./types";

View file

@ -1,5 +1,4 @@
'use server';
import 'server-only';
import { sew } from "@/actions";
import { withOptionalAuthV2 } from "@/withAuthV2";
import { PrismaClient, Repo } from "@sourcebot/db";