From 0942d094b7b222edc9cf468ab589523f45e435db Mon Sep 17 00:00:00 2001 From: aliang Date: Thu, 19 Dec 2024 12:03:40 +0700 Subject: [PATCH] feat(chat): display git repo connected status in chat side panel (#3550) * feat(ui): resolve indexed repository of IDE workspace * [autofix.ci] apply automated fixes * update: sync git url * update: lint * update: lint * update: animation * update * feat(ui): add repo select in chat sidebar * update * update: set default repo * update: code_query * [autofix.ci] apply automated fixes * update: focus * update: type * Update ee/tabby-ui/components/chat/repo-select.tsx * Apply suggestions from code review * update * update: styling * Update ee/tabby-ui/components/chat/repo-select.tsx * [autofix.ci] apply automated fixes * update: optional * update * update: chat panel api * [autofix.ci] apply automated fixes * update: init * update: init * update: revert * update * Update clients/tabby-chat-panel/src/index.ts Co-authored-by: Meng Zhang * Update clients/tabby-chat-panel/src/index.ts Co-authored-by: Meng Zhang * Update clients/tabby-chat-panel/src/index.ts Co-authored-by: Meng Zhang * update: rename * update * update: test * update * update: remove * update: test * update * update: test cases --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Meng Zhang --- clients/tabby-chat-panel/src/index.ts | 11 ++ ee/tabby-schema/src/schema/mod.rs | 2 +- ee/tabby-ui/app/chat/page.tsx | 12 ++ ee/tabby-ui/components/chat/chat-panel.tsx | 26 ++- ee/tabby-ui/components/chat/chat.tsx | 144 +++++++++------ ee/tabby-ui/components/chat/prompt-form.tsx | 16 +- ee/tabby-ui/components/chat/repo-select.tsx | 193 ++++++++++++++++++++ ee/tabby-ui/components/source-icon.tsx | 34 ++++ ee/tabby-ui/lib/utils/index.ts | 1 + ee/tabby-ui/lib/utils/repository.ts | 24 +++ ee/tabby-ui/package.json | 6 +- ee/tabby-ui/test/index.test.ts | 102 +++++++++++ ee/tabby-ui/tsconfig.json | 6 +- pnpm-lock.yaml | 136 +++++++++++++- 14 files changed, 646 insertions(+), 67 deletions(-) create mode 100644 ee/tabby-ui/components/chat/repo-select.tsx create mode 100644 ee/tabby-ui/components/source-icon.tsx create mode 100644 ee/tabby-ui/lib/utils/repository.ts create mode 100644 ee/tabby-ui/test/index.test.ts diff --git a/clients/tabby-chat-panel/src/index.ts b/clients/tabby-chat-panel/src/index.ts index da6a46aa0512..08f419e295f7 100644 --- a/clients/tabby-chat-panel/src/index.ts +++ b/clients/tabby-chat-panel/src/index.ts @@ -177,6 +177,13 @@ export interface SymbolInfo { target: FileLocation } +/** + * Includes information about a git repository in workspace folder + */ +export interface GitRepository { + url: string +} + export interface ServerApi { init: (request: InitRequest) => void sendMessage: (message: ChatMessage) => void @@ -224,6 +231,9 @@ export interface ClientApiMethods { * @returns Whether the file location is opened successfully. */ openInEditor: (target: FileLocation) => Promise + + // Provide all repos found in workspace folders. + readWorkspaceGitRepositories?: () => Promise } export interface ClientApi extends ClientApiMethods { @@ -258,6 +268,7 @@ export function createClient(target: HTMLIFrameElement, api: ClientApiMethods): onKeyboardEvent: api.onKeyboardEvent, lookupSymbol: api.lookupSymbol, openInEditor: api.openInEditor, + readWorkspaceGitRepositories: api.readWorkspaceGitRepositories, }, }) } diff --git a/ee/tabby-schema/src/schema/mod.rs b/ee/tabby-schema/src/schema/mod.rs index ffef5bdc5ee9..c6c75e6e954d 100644 --- a/ee/tabby-schema/src/schema/mod.rs +++ b/ee/tabby-schema/src/schema/mod.rs @@ -541,7 +541,7 @@ impl Query { } async fn repository_list(ctx: &Context) -> Result> { - let user = check_user(ctx).await?; + let user = check_user_allow_auth_token(ctx).await?; ctx.locator .repository() diff --git a/ee/tabby-ui/app/chat/page.tsx b/ee/tabby-ui/app/chat/page.tsx index 48287de58c56..3fdc0d0f2c67 100644 --- a/ee/tabby-ui/app/chat/page.tsx +++ b/ee/tabby-ui/app/chat/page.tsx @@ -75,6 +75,10 @@ export default function ChatPage() { const [supportsOnApplyInEditorV2, setSupportsOnApplyInEditorV2] = useState(false) const [supportsOnLookupSymbol, setSupportsOnLookupSymbol] = useState(false) + const [ + supportsProvideWorkspaceGitRepoInfo, + setSupportsProvideWorkspaceGitRepoInfo + ] = useState(false) const sendMessage = (message: ChatMessage) => { if (chatRef.current) { @@ -238,6 +242,9 @@ export default function ChatPage() { ?.hasCapability('onApplyInEditorV2') .then(setSupportsOnApplyInEditorV2) server?.hasCapability('lookupSymbol').then(setSupportsOnLookupSymbol) + server + ?.hasCapability('readWorkspaceGitRepositories') + .then(setSupportsProvideWorkspaceGitRepoInfo) } checkCapabilities() @@ -395,6 +402,11 @@ export default function ChatPage() { (supportsOnLookupSymbol ? server?.lookupSymbol : undefined) } openInEditor={isInEditor && server?.openInEditor} + readWorkspaceGitRepositories={ + isInEditor && supportsProvideWorkspaceGitRepoInfo + ? server?.readWorkspaceGitRepositories + : undefined + } /> ) diff --git a/ee/tabby-ui/components/chat/chat-panel.tsx b/ee/tabby-ui/components/chat/chat-panel.tsx index b9f41fa47a0d..92d5a7746e7e 100644 --- a/ee/tabby-ui/components/chat/chat-panel.tsx +++ b/ee/tabby-ui/components/chat/chat-panel.tsx @@ -20,6 +20,7 @@ import { IconCheck, IconEye, IconEyeOff, + IconFileText, IconRefresh, IconRemove, IconShare, @@ -32,6 +33,7 @@ import { FooterText } from '@/components/footer' import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip' import { ChatContext } from './chat' +import { RepoSelect } from './repo-select' export interface ChatPanelProps extends Pick { @@ -70,11 +72,16 @@ function ChatPanelRenderer( relevantContext, removeRelevantContext, activeSelection, - onCopyContent + onCopyContent, + selectedRepoId, + setSelectedRepoId, + repos, + initialized } = React.useContext(ChatContext) const enableActiveSelection = useChatStore( state => state.enableActiveSelection ) + const [persisting, setPerisiting] = useState(false) const { width } = useWindowSize() const isExtraSmallScreen = typeof width === 'number' && width < 376 @@ -124,6 +131,14 @@ function ChatPanelRenderer( } } + const onSelectRepo = (sourceId: string | undefined) => { + setSelectedRepoId(sourceId) + + setTimeout(() => { + chatInputRef.current?.focus() + }) + } + React.useImperativeHandle( ref, () => { @@ -220,8 +235,15 @@ function ChatPanelRenderer(
+ {activeSelection ? ( +
diff --git a/ee/tabby-ui/components/chat/chat.tsx b/ee/tabby-ui/components/chat/chat.tsx index 4600d5d69026..360afcec4bc7 100644 --- a/ee/tabby-ui/components/chat/chat.tsx +++ b/ee/tabby-ui/components/chat/chat.tsx @@ -4,17 +4,21 @@ import type { Context, FileContext, FileLocation, + GitRepository, LookupSymbolHint, NavigateOpts, SymbolInfo } from 'tabby-chat-panel' +import { useQuery } from 'urql' import { ERROR_CODE_NOT_FOUND } from '@/lib/constants' +import { graphql } from '@/lib/gql/generates' import { CodeQueryInput, CreateMessageInput, InputMaybe, MessageAttachmentCodeInput, + RepositorySourceListQuery, ThreadRunOptionsInput } from '@/lib/gql/generates/graphql' import { useDebounceCallback } from '@/lib/hooks/use-debounce' @@ -30,15 +34,29 @@ import { UserMessage, UserMessageWithOptionalId } from '@/lib/types/chat' -import { cn, nanoid } from '@/lib/utils' +import { cn, findClosestGitRepository, nanoid } from '@/lib/utils' -import { ListSkeleton } from '../skeleton' import { ChatPanel, ChatPanelRef } from './chat-panel' import { ChatScrollAnchor } from './chat-scroll-anchor' import { EmptyScreen } from './empty-screen' import { QuestionAnswerList } from './question-answer' +const repositoryListQuery = graphql(/* GraphQL */ ` + query RepositorySourceList { + repositoryList { + id + name + kind + gitUrl + sourceId + sourceName + sourceKind + } + } +`) + type ChatContextValue = { + initialized: boolean threadId: string | undefined isLoading: boolean qaPairs: QuestionAnswerPair[] @@ -63,6 +81,10 @@ type ChatContextValue = { removeRelevantContext: (index: number) => void chatInputRef: RefObject supportsOnApplyInEditorV2: boolean + selectedRepoId: string | undefined + setSelectedRepoId: React.Dispatch> + repos: RepositorySourceListQuery['repositoryList'] | undefined + fetchingRepos: boolean } export const ChatContext = React.createContext( @@ -103,6 +125,7 @@ interface ChatProps extends React.ComponentProps<'div'> { openInEditor?: (target: FileLocation) => void chatInputRef: RefObject supportsOnApplyInEditorV2: boolean + readWorkspaceGitRepositories?: () => Promise } function ChatRenderer( @@ -125,11 +148,12 @@ function ChatRenderer( onLookupSymbol, openInEditor, chatInputRef, - supportsOnApplyInEditorV2 + supportsOnApplyInEditorV2, + readWorkspaceGitRepositories }: ChatProps, ref: React.ForwardedRef ) { - const [initialized, setInitialzed] = React.useState(false) + const [initialized, setInitialized] = React.useState(false) const [threadId, setThreadId] = React.useState() const isOnLoadExecuted = React.useRef(false) const [qaPairs, setQaPairs] = React.useState(initialMessages ?? []) @@ -138,11 +162,22 @@ function ChatRenderer( const [activeSelection, setActiveSelection] = React.useState( null ) + // sourceId + const [selectedRepoId, setSelectedRepoId] = React.useState< + string | undefined + >() + const enableActiveSelection = useChatStore( state => state.enableActiveSelection ) + const chatPanelRef = React.useRef(null) + const [{ data: repositoryListData, fetching: fetchingRepos }] = useQuery({ + query: repositoryListQuery + }) + const repos = repositoryListData?.repositoryList + const { sendUserMessage, isLoading, @@ -196,8 +231,7 @@ function ChatRenderer( ] setQaPairs(nextQaPairs) const [userMessage, threadRunOptions] = generateRequestPayload( - qaPair.user, - enableActiveSelection + qaPair.user ) return regenerate({ @@ -354,30 +388,13 @@ function ChatRenderer( }, [error]) const generateRequestPayload = ( - userMessage: UserMessage, - enableActiveSelection?: boolean + userMessage: UserMessage ): [CreateMessageInput, ThreadRunOptionsInput] => { - // use selectContext for code query by default - let contextForCodeQuery: FileContext | undefined = userMessage.selectContext - - // if enableActiveSelection, use selectContext or activeContext for code query - if (enableActiveSelection) { - contextForCodeQuery = contextForCodeQuery || userMessage.activeContext - } - - // check context for codeQuery - if (!isValidContextForCodeQuery(contextForCodeQuery)) { - contextForCodeQuery = undefined - } - - const codeQuery: InputMaybe = contextForCodeQuery + const content = userMessage.message + const codeQuery: InputMaybe = selectedRepoId ? { - content: contextForCodeQuery.content ?? '', - filepath: contextForCodeQuery.filepath, - language: contextForCodeQuery.filepath - ? filename2prism(contextForCodeQuery.filepath)[0] || 'plaintext' - : 'plaintext', - gitUrl: contextForCodeQuery?.git_url ?? '' + content, + sourceId: selectedRepoId } : null @@ -385,6 +402,7 @@ function ChatRenderer( enableActiveSelection && !!userMessage.activeContext const clientSideFileContexts: FileContext[] = uniqWith( compact([ + userMessage.selectContext, hasUsableActiveContext && userMessage.activeContext, ...(userMessage?.relevantContext || []) ]), @@ -398,8 +416,6 @@ function ChatRenderer( startLine: o.range.start })) - const content = userMessage.message - return [ { content, @@ -459,9 +475,7 @@ function ChatRenderer( setQaPairs(nextQaPairs) - sendUserMessage( - ...generateRequestPayload(newUserMessage, enableActiveSelection) - ) + sendUserMessage(...generateRequestPayload(newUserMessage)) } ) @@ -511,6 +525,43 @@ function ChatRenderer( debouncedUpdateActiveSelection.run(ctx) } + const fetchWorkspaceGitRepo = () => { + if (readWorkspaceGitRepositories) { + return readWorkspaceGitRepositories() + } else { + return [] + } + } + + React.useEffect(() => { + const init = async () => { + const workspaceGitRepositories = await fetchWorkspaceGitRepo() + // get default repo + if (workspaceGitRepositories?.length && repos?.length) { + const defaultGitUrl = workspaceGitRepositories[0].url + const repo = findClosestGitRepository( + repos.map(x => ({ url: x.gitUrl, sourceId: x.sourceId })), + defaultGitUrl + ) + if (repo) { + setSelectedRepoId(repo.sourceId) + } + } + + setInitialized(true) + } + + if (!fetchingRepos && !initialized) { + init() + } + }, [fetchingRepos]) + + React.useEffect(() => { + if (initialized) { + onLoaded?.() + } + }, [initialized]) + React.useImperativeHandle( ref, () => { @@ -526,17 +577,7 @@ function ChatRenderer( [] ) - React.useEffect(() => { - setInitialzed(true) - onLoaded?.() - }, []) - const chatMaxWidthClass = maxWidth ? `max-w-${maxWidth}` : 'max-w-2xl' - if (!initialized) { - return ( - - ) - } return (
@@ -624,13 +670,3 @@ function formatThreadRunErrorMessage(error: ExtendedCombinedError | undefined) { return error.message || 'Failed to fetch' } - -function isValidContextForCodeQuery(context: FileContext | undefined) { - if (!context) return false - - const isUntitledFile = - context.filepath.startsWith('untitled:') && - !filename2prism(context.filepath)[0] - - return !isUntitledFile -} diff --git a/ee/tabby-ui/components/chat/prompt-form.tsx b/ee/tabby-ui/components/chat/prompt-form.tsx index abe51405a278..98fcd56d54ec 100644 --- a/ee/tabby-ui/components/chat/prompt-form.tsx +++ b/ee/tabby-ui/components/chat/prompt-form.tsx @@ -32,6 +32,7 @@ export interface PromptProps onSubmit: (value: string) => Promise isLoading: boolean chatInputRef: React.RefObject + isInitializing?: boolean } export interface PromptFormRef { @@ -39,7 +40,14 @@ export interface PromptFormRef { } function PromptFormRenderer( - { onSubmit, input, setInput, isLoading, chatInputRef }: PromptProps, + { + onSubmit, + input, + setInput, + isLoading, + chatInputRef, + isInitializing + }: PromptProps, ref: React.ForwardedRef ) { const { formRef, onKeyDown } = useEnterSubmit() @@ -137,7 +145,7 @@ function PromptFormRenderer( HTMLFormElement > = async e => { e.preventDefault() - if (!input?.trim() || isLoading) { + if (!input?.trim() || isLoading || isInitializing) { return } @@ -212,7 +220,7 @@ function PromptFormRenderer( rows={1} placeholder="Ask a question." spellCheck={false} - className="min-h-[60px] w-full resize-none bg-transparent px-4 py-[1.3rem] focus-within:outline-none" + className="min-h-[60px] w-full resize-none bg-transparent py-[1.3rem] pr-4 focus-within:outline-none sm:pl-4" value={input} ref={chatInputRef} onChange={e => { @@ -233,7 +241,7 @@ function PromptFormRenderer(
+ } + > + + + +
+ {selectedRepo ? ( + + ) : ( + + )} +
+ + {selectedRepoName || 'Workspace'} + +
+ {!value && ( +
+ +
+ )} +
+
+ {!!value && ( + + )} +
+ + + + + No context found + + {repos?.map(repo => { + const isSelected = repo.sourceId === value + + return ( + { + onSelectRepo(repo.sourceId) + setOpen(false) + }} + title={repo.sourceName} + > + +
+ +
+ {repo.sourceName} +
+
+
+ ) + })} +
+
+
+
+
+ + ) +} diff --git a/ee/tabby-ui/components/source-icon.tsx b/ee/tabby-ui/components/source-icon.tsx new file mode 100644 index 000000000000..01d78eaa0206 --- /dev/null +++ b/ee/tabby-ui/components/source-icon.tsx @@ -0,0 +1,34 @@ +import { ReactNode } from 'react' + +import { ContextSourceKind } from '@/lib/gql/generates/graphql' +import { + IconCode, + IconEmojiBook, + IconEmojiGlobe, + IconGitHub, + IconGitLab +} from '@/components/ui/icons' + +interface SourceIconProps { + kind: ContextSourceKind + gitIcon?: ReactNode + className?: string +} + +export function SourceIcon({ kind, gitIcon, ...props }: SourceIconProps) { + switch (kind) { + case ContextSourceKind.Doc: + return + case ContextSourceKind.Web: + return + case ContextSourceKind.Github: + return + case ContextSourceKind.Gitlab: + return + case ContextSourceKind.Git: + // for custom git icon + return gitIcon || + default: + return null + } +} diff --git a/ee/tabby-ui/lib/utils/index.ts b/ee/tabby-ui/lib/utils/index.ts index b68738695766..218487694347 100644 --- a/ee/tabby-ui/lib/utils/index.ts +++ b/ee/tabby-ui/lib/utils/index.ts @@ -8,6 +8,7 @@ import { AttachmentCodeItem, AttachmentDocItem } from '@/lib/types' import { Maybe } from '../gql/generates/graphql' export * from './chat' +export * from './repository' export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) diff --git a/ee/tabby-ui/lib/utils/repository.ts b/ee/tabby-ui/lib/utils/repository.ts new file mode 100644 index 000000000000..1689118f2bf2 --- /dev/null +++ b/ee/tabby-ui/lib/utils/repository.ts @@ -0,0 +1,24 @@ +import gitUrlParse from 'git-url-parse' +import type { GitRepository } from 'tabby-chat-panel' + +export function findClosestGitRepository( + repositories: T[], + gitUrl: string +): T | undefined { + const targetSearch = gitUrlParse(gitUrl) + if (!targetSearch) { + return undefined + } + + const filteredRepos = repositories.filter(repo => { + const search = gitUrlParse(repo.url) + return search.name === targetSearch.name + }) + + if (filteredRepos.length === 0) { + return undefined + } else { + // If there're multiple matches, we pick the one with highest alphabetical order + return filteredRepos.sort((a, b) => a.url.localeCompare(b.url))[0] + } +} diff --git a/ee/tabby-ui/package.json b/ee/tabby-ui/package.json index f5d8d8bc420e..f44474941979 100644 --- a/ee/tabby-ui/package.json +++ b/ee/tabby-ui/package.json @@ -11,6 +11,7 @@ "start": "next start", "lint": "next lint && pnpm format:check", "lint:fix": "next lint --fix && pnpm format:write", + "test": "vitest run", "preview": "next build && next start", "type-check": "tsc --noEmit", "format:write": "prettier --write \"{app,lib,components}/**/*.{ts,tsx,mdx}\" --cache", @@ -77,6 +78,7 @@ "focus-trap-react": "^10.1.1", "framer-motion": "^11.11.7", "fuzzysort": "^3.0.2", + "git-url-parse": "^16.0.0", "graphql": "^16.8.1", "graphql-ws": "^5.16.0", "humanize-duration": "^3.31.0", @@ -134,6 +136,7 @@ "@tailwindcss/typography": "^0.5.9", "@types/color": "^3.0.6", "@types/dompurify": "^3.0.5", + "@types/git-url-parse": "^9.0.3", "@types/he": "^1.2.3", "@types/humanize-duration": "^3.27.4", "@types/lodash-es": "^4.17.10", @@ -162,6 +165,7 @@ "tailwind-merge": "^1.12.0", "tailwindcss": "^3.3.1", "tailwindcss-animate": "^1.0.5", - "typescript": "^5.1.3" + "typescript": "^5.1.3", + "vitest": "^1.5.2" } } diff --git a/ee/tabby-ui/test/index.test.ts b/ee/tabby-ui/test/index.test.ts new file mode 100644 index 000000000000..d65939cde364 --- /dev/null +++ b/ee/tabby-ui/test/index.test.ts @@ -0,0 +1,102 @@ +import { describe, expect, it } from 'vitest' +import { findClosestGitRepository } from '../lib/utils/repository' +import type { GitRepository } from 'tabby-chat-panel' + +describe('findClosestGitRepository', () => { + it('should match .git suffix', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/example/test' }, + ] + const result = findClosestGitRepository(repositories, 'https://github.com/example/test.git') + expect(result).toEqual(repositories[0]) + }) + + it('should match auth in URL', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/example/test' }, + ] + const result = findClosestGitRepository(repositories, 'https://creds@github.com/example/test') + expect(result).toEqual(repositories[0]) + }) + + it('should not match different names', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/example/anoth-repo' }, + ] + const result = findClosestGitRepository(repositories, 'https://github.com/example/another-repo') + expect(result).toBeUndefined() + }) + + it('should not match repositories with a common prefix', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/TabbyML/registry-tabby' }, + ] + const result = findClosestGitRepository(repositories, 'https://github.com/TabbyML/tabby') + expect(result).toBeUndefined() + }) + + it('should not match entirely different repository names', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/TabbyML/uptime' }, + ] + const result = findClosestGitRepository(repositories, 'https://github.com/TabbyML/tabby') + expect(result).toBeUndefined() + }) + + it('should not match URL without repository name', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/TabbyML/tabby' }, + ] + const result = findClosestGitRepository(repositories, 'https://github.com') + expect(result).toBeUndefined() + }) + + it('should match different host', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/TabbyML/tabby' }, + ] + const result = findClosestGitRepository(repositories, 'https://bitbucket.com/TabbyML/tabby') + expect(result).toEqual(repositories[0]) + }) + + it('should not match multiple close matches', () => { + const repositories: GitRepository[] = [ + { url: 'https://bitbucket.com/CrabbyML/crabby' }, + { url: 'https://gitlab.com/TabbyML/registry-tabby' }, + ] + const result = findClosestGitRepository(repositories, 'git@github.com:TabbyML/tabby') + expect(result).toBeUndefined() + }) + + it('should match different protocol and suffix', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/TabbyML/tabby' }, + ] + const result = findClosestGitRepository(repositories, 'git@github.com:TabbyML/tabby.git') + expect(result).toEqual(repositories[0]) + }) + + it('should match different protocol', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/TabbyML/tabby' }, + ] + const result = findClosestGitRepository(repositories, 'git@github.com:TabbyML/tabby') + expect(result).toEqual(repositories[0]) + }) + + it('should match URL without organization', () => { + const repositories: GitRepository[] = [ + { url: 'https://custom-git.com/TabbyML/tabby' }, + ] + const result = findClosestGitRepository(repositories, 'https://custom-git.com/tabby') + expect(result).toEqual(repositories[0]) + }) + + it('should match local URL', () => { + const repositories: GitRepository[] = [ + { url: 'file:///home/TabbyML/tabby' }, + ] + const result = findClosestGitRepository(repositories, 'git@github.com:TabbyML/tabby.git') + expect(result).toEqual(repositories[0]) + }) +}) diff --git a/ee/tabby-ui/tsconfig.json b/ee/tabby-ui/tsconfig.json index a625d1aef0af..a62cc00a466e 100644 --- a/ee/tabby-ui/tsconfig.json +++ b/ee/tabby-ui/tsconfig.json @@ -29,7 +29,9 @@ "next-auth.d.ts", "**/*.ts", "**/*.tsx", - ".next/types/**/*.ts" -, "lib/patch-fetch.js" ], + ".next/types/**/*.ts", + "lib/patch-fetch.js", + "test" + ], "exclude": ["node_modules"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94d67021249f..54a06d0b907a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -360,10 +360,10 @@ importers: version: 5.2.0 esbuild-plugin-copy: specifier: ^2.1.1 - version: 2.1.1(esbuild@0.19.12) + version: 2.1.1(esbuild@0.20.2) esbuild-plugin-polyfill-node: specifier: ^0.3.0 - version: 0.3.0(esbuild@0.19.12) + version: 0.3.0(esbuild@0.20.2) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -601,6 +601,9 @@ importers: fuzzysort: specifier: ^3.0.2 version: 3.0.2 + git-url-parse: + specifier: ^16.0.0 + version: 16.0.0 graphql: specifier: ^16.8.1 version: 16.8.1 @@ -767,6 +770,9 @@ importers: '@types/dompurify': specifier: ^3.0.5 version: 3.0.5 + '@types/git-url-parse': + specifier: ^9.0.3 + version: 9.0.3 '@types/he': specifier: ^1.2.3 version: 1.2.3 @@ -854,6 +860,9 @@ importers: typescript: specifier: ^5.1.3 version: 5.2.2 + vitest: + specifier: ^1.5.2 + version: 1.6.0(@types/node@17.0.45)(terser@5.31.0) packages: @@ -3944,6 +3953,9 @@ packages: '@types/fs-extra@11.0.4': resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + '@types/git-url-parse@9.0.3': + resolution: {integrity: sha512-Wrb8zeghhpKbYuqAOg203g+9YSNlrZWNZYvwxJuDF4dTmerijqpnGbI79yCuPtHSXHPEwv1pAFUB4zsSqn82Og==} + '@types/glob@7.2.0': resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} @@ -4022,6 +4034,9 @@ packages: '@types/object-hash@3.0.6': resolution: {integrity: sha512-fOBV8C1FIu2ELinoILQ+ApxcUKz4ngq+IWUYrxSGjXzzjUALijilampwkMgEtJ+h2njAW3pi853QpzNVCHB73w==} + '@types/parse-path@7.0.3': + resolution: {integrity: sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==} + '@types/parse5@6.0.3': resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} @@ -6513,6 +6528,12 @@ packages: resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==} hasBin: true + git-up@8.0.0: + resolution: {integrity: sha512-uBI8Zdt1OZlrYfGcSVroLJKgyNNXlgusYFzHk614lTasz35yg2PVpL1RMy0LOO2dcvF9msYW3pRfUSmafZNrjg==} + + git-url-parse@16.0.0: + resolution: {integrity: sha512-Y8iAF0AmCaqXc6a5GYgPQW9ESbncNLOL+CeQAJRhmWUOmnPkKpBYeWYp4mFd3LA5j53CdGDdslzX12yEBVHQQg==} + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -7104,6 +7125,9 @@ packages: resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} engines: {node: '>= 0.4'} + is-ssh@1.4.0: + resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -8324,9 +8348,16 @@ packages: resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} engines: {node: '>=0.10.0'} + parse-path@7.0.0: + resolution: {integrity: sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==} + parse-semver@1.1.1: resolution: {integrity: sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==} + parse-url@9.2.0: + resolution: {integrity: sha512-bCgsFI+GeGWPAvAiUv63ZorMeif3/U0zaXABGJbOWt5OH2KCaPHF6S+0ok4aqM9RuIPGyZdx9tR9l13PsW4AYQ==} + engines: {node: '>=14.13.0'} + parse5-htmlparser2-tree-adapter@7.0.0: resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} @@ -8866,6 +8897,9 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + protocols@2.0.1: + resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==} + ps-tree@1.2.0: resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} engines: {node: '>= 0.10'} @@ -14351,6 +14385,8 @@ snapshots: '@types/jsonfile': 6.1.4 '@types/node': 20.12.12 + '@types/git-url-parse@9.0.3': {} + '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 @@ -14425,6 +14461,8 @@ snapshots: '@types/object-hash@3.0.6': {} + '@types/parse-path@7.0.3': {} + '@types/parse5@6.0.3': {} '@types/prismjs@1.26.4': {} @@ -16722,11 +16760,11 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.0.4 - esbuild-plugin-copy@2.1.1(esbuild@0.19.12): + esbuild-plugin-copy@2.1.1(esbuild@0.20.2): dependencies: chalk: 4.1.2 chokidar: 3.6.0 - esbuild: 0.19.12 + esbuild: 0.20.2 fs-extra: 10.1.0 globby: 11.1.0 @@ -16736,6 +16774,12 @@ snapshots: esbuild: 0.19.12 import-meta-resolve: 3.1.1 + esbuild-plugin-polyfill-node@0.3.0(esbuild@0.20.2): + dependencies: + '@jspm/core': 2.0.1 + esbuild: 0.20.2 + import-meta-resolve: 3.1.1 + esbuild@0.19.11: optionalDependencies: '@esbuild/aix-ppc64': 0.19.11 @@ -17755,6 +17799,15 @@ snapshots: pathe: 1.1.2 tar: 6.2.1 + git-up@8.0.0: + dependencies: + is-ssh: 1.4.0 + parse-url: 9.2.0 + + git-url-parse@16.0.0: + dependencies: + git-up: 8.0.0 + github-from-package@0.0.0: optional: true @@ -18432,6 +18485,10 @@ snapshots: dependencies: call-bind: 1.0.7 + is-ssh@1.4.0: + dependencies: + protocols: 2.0.1 + is-stream@2.0.1: {} is-stream@3.0.0: {} @@ -19956,10 +20013,19 @@ snapshots: parse-passwd@1.0.0: {} + parse-path@7.0.0: + dependencies: + protocols: 2.0.1 + parse-semver@1.1.1: dependencies: semver: 5.7.2 + parse-url@9.2.0: + dependencies: + '@types/parse-path': 7.0.3 + parse-path: 7.0.0 + parse5-htmlparser2-tree-adapter@7.0.0: dependencies: domhandler: 5.0.3 @@ -20533,6 +20599,8 @@ snapshots: proto-list@1.2.4: {} + protocols@2.0.1: {} + ps-tree@1.2.0: dependencies: event-stream: 3.3.4 @@ -22466,6 +22534,23 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 + vite-node@1.6.0(@types/node@17.0.45)(terser@5.31.0): + dependencies: + cac: 6.7.14 + debug: 4.3.4(supports-color@9.4.0) + pathe: 1.1.2 + picocolors: 1.0.1 + vite: 5.2.11(@types/node@17.0.45)(terser@5.31.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + vite-node@1.6.0(@types/node@20.12.12)(terser@5.31.0): dependencies: cac: 6.7.14 @@ -22483,6 +22568,16 @@ snapshots: - supports-color - terser + vite@5.2.11(@types/node@17.0.45)(terser@5.31.0): + dependencies: + esbuild: 0.20.2 + postcss: 8.4.38 + rollup: 4.27.4 + optionalDependencies: + '@types/node': 17.0.45 + fsevents: 2.3.3 + terser: 5.31.0 + vite@5.2.11(@types/node@20.12.12)(terser@5.31.0): dependencies: esbuild: 0.20.2 @@ -22493,6 +22588,39 @@ snapshots: fsevents: 2.3.3 terser: 5.31.0 + vitest@1.6.0(@types/node@17.0.45)(terser@5.31.0): + dependencies: + '@vitest/expect': 1.6.0 + '@vitest/runner': 1.6.0 + '@vitest/snapshot': 1.6.0 + '@vitest/spy': 1.6.0 + '@vitest/utils': 1.6.0 + acorn-walk: 8.3.2 + chai: 4.4.1 + debug: 4.3.4(supports-color@9.4.0) + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.10 + pathe: 1.1.2 + picocolors: 1.0.1 + std-env: 3.7.0 + strip-literal: 2.1.0 + tinybench: 2.8.0 + tinypool: 0.8.4 + vite: 5.2.11(@types/node@17.0.45)(terser@5.31.0) + vite-node: 1.6.0(@types/node@17.0.45)(terser@5.31.0) + why-is-node-running: 2.2.2 + optionalDependencies: + '@types/node': 17.0.45 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + vitest@1.6.0(@types/node@20.12.12)(terser@5.31.0): dependencies: '@vitest/expect': 1.6.0