From c32eebf57d88af4a0799bc70f656ee2a8bd673e4 Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Thu, 26 Mar 2026 14:37:17 +0800 Subject: [PATCH] refactor: use ungh for github api (#34108) --- web/.env.example | 2 - .../plugins/plugin-install-flow.test.ts | 41 ++--- .../github-star/__tests__/index.spec.tsx | 51 ++++-- .../components/header/github-star/index.tsx | 24 +-- .../install-plugin/__tests__/hooks.spec.ts | 43 ++--- .../plugins/install-plugin/hooks.ts | 152 +++++++--------- .../__tests__/index.spec.tsx | 12 +- .../install-from-github/index.tsx | 3 +- .../steps/__tests__/selectPackage.spec.tsx | 14 +- .../steps/selectPackage.tsx | 3 +- .../__tests__/detail-header.spec.tsx | 10 +- .../__tests__/use-plugin-operations.spec.ts | 10 +- .../hooks/use-plugin-operations.ts | 3 +- .../plugin-item/__tests__/action.spec.tsx | 12 +- .../components/plugins/plugin-item/action.tsx | 3 +- .../repos/[owner]/[repo]/releases/route.ts | 37 ---- web/config/index.ts | 3 - web/docs/test.md | 10 -- web/env.ts | 5 - web/eslint-suppressions.json | 39 ++++ web/eslint.config.mjs | 2 + web/models/common.ts | 4 - web/next/server.ts | 2 - web/package.json | 3 - web/pnpm-lock.yaml | 168 ------------------ web/proxy.ts | 8 +- 26 files changed, 236 insertions(+), 428 deletions(-) delete mode 100644 web/app/repos/[owner]/[repo]/releases/route.ts delete mode 100644 web/next/server.ts diff --git a/web/.env.example b/web/.env.example index 079c3bdeef..62d4fa6c56 100644 --- a/web/.env.example +++ b/web/.env.example @@ -51,8 +51,6 @@ NEXT_PUBLIC_ALLOW_EMBED= # Allow rendering unsafe URLs which have "data:" scheme. NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME=false -# Github Access Token, used for invoking Github API -NEXT_PUBLIC_GITHUB_ACCESS_TOKEN= # The maximum number of top-k value for RAG. NEXT_PUBLIC_TOP_K_MAX_VALUE=10 diff --git a/web/__tests__/plugins/plugin-install-flow.test.ts b/web/__tests__/plugins/plugin-install-flow.test.ts index 8fa2246198..dd5a18b724 100644 --- a/web/__tests__/plugins/plugin-install-flow.test.ts +++ b/web/__tests__/plugins/plugin-install-flow.test.ts @@ -5,11 +5,9 @@ * upload handling, and task status polling. Verifies the complete plugin * installation pipeline from source discovery to completion. */ -import { beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@/config', () => ({ - GITHUB_ACCESS_TOKEN: '', -})) +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { checkForUpdates, fetchReleases, handleUpload } from '@/app/components/plugins/install-plugin/hooks' const mockToastNotify = vi.fn() vi.mock('@/app/components/base/ui/toast', () => ({ @@ -30,10 +28,6 @@ vi.mock('@/service/plugins', () => ({ checkTaskStatus: vi.fn(), })) -const { useGitHubReleases, useGitHubUpload } = await import( - '@/app/components/plugins/install-plugin/hooks', -) - describe('Plugin Installation Flow Integration', () => { beforeEach(() => { vi.clearAllMocks() @@ -44,22 +38,22 @@ describe('Plugin Installation Flow Integration', () => { it('fetches releases, checks for updates, and uploads the new version', async () => { const mockReleases = [ { - tag_name: 'v2.0.0', - assets: [{ browser_download_url: 'https://github.com/test/v2.difypkg', name: 'plugin-v2.difypkg' }], + tag: 'v2.0.0', + assets: [{ downloadUrl: 'https://github.com/test/v2.difypkg' }], }, { - tag_name: 'v1.5.0', - assets: [{ browser_download_url: 'https://github.com/test/v1.5.difypkg', name: 'plugin-v1.5.difypkg' }], + tag: 'v1.5.0', + assets: [{ downloadUrl: 'https://github.com/test/v1.5.difypkg' }], }, { - tag_name: 'v1.0.0', - assets: [{ browser_download_url: 'https://github.com/test/v1.difypkg', name: 'plugin-v1.difypkg' }], + tag: 'v1.0.0', + assets: [{ downloadUrl: 'https://github.com/test/v1.difypkg' }], }, ] ;(globalThis.fetch as ReturnType).mockResolvedValue({ ok: true, - json: () => Promise.resolve(mockReleases), + json: () => Promise.resolve({ releases: mockReleases }), }) mockUploadGitHub.mockResolvedValue({ @@ -67,8 +61,6 @@ describe('Plugin Installation Flow Integration', () => { unique_identifier: 'test-plugin:2.0.0', }) - const { fetchReleases, checkForUpdates } = useGitHubReleases() - const releases = await fetchReleases('test-org', 'test-repo') expect(releases).toHaveLength(3) expect(releases[0].tag_name).toBe('v2.0.0') @@ -77,7 +69,6 @@ describe('Plugin Installation Flow Integration', () => { expect(needUpdate).toBe(true) expect(toastProps.message).toContain('v2.0.0') - const { handleUpload } = useGitHubUpload() const onSuccess = vi.fn() const result = await handleUpload( 'https://github.com/test-org/test-repo', @@ -104,18 +95,16 @@ describe('Plugin Installation Flow Integration', () => { it('handles no new version available', async () => { const mockReleases = [ { - tag_name: 'v1.0.0', - assets: [{ browser_download_url: 'https://github.com/test/v1.difypkg', name: 'plugin-v1.difypkg' }], + tag: 'v1.0.0', + assets: [{ downloadUrl: 'https://github.com/test/v1.difypkg' }], }, ] ;(globalThis.fetch as ReturnType).mockResolvedValue({ ok: true, - json: () => Promise.resolve(mockReleases), + json: () => Promise.resolve({ releases: mockReleases }), }) - const { fetchReleases, checkForUpdates } = useGitHubReleases() - const releases = await fetchReleases('test-org', 'test-repo') const { needUpdate, toastProps } = checkForUpdates(releases, 'v1.0.0') @@ -127,11 +116,9 @@ describe('Plugin Installation Flow Integration', () => { it('handles empty releases', async () => { ;(globalThis.fetch as ReturnType).mockResolvedValue({ ok: true, - json: () => Promise.resolve([]), + json: () => Promise.resolve({ releases: [] }), }) - const { fetchReleases, checkForUpdates } = useGitHubReleases() - const releases = await fetchReleases('test-org', 'test-repo') expect(releases).toHaveLength(0) @@ -147,7 +134,6 @@ describe('Plugin Installation Flow Integration', () => { status: 404, }) - const { fetchReleases } = useGitHubReleases() const releases = await fetchReleases('nonexistent-org', 'nonexistent-repo') expect(releases).toEqual([]) @@ -159,7 +145,6 @@ describe('Plugin Installation Flow Integration', () => { it('handles upload failure gracefully', async () => { mockUploadGitHub.mockRejectedValue(new Error('Upload failed')) - const { handleUpload } = useGitHubUpload() const onSuccess = vi.fn() await expect( diff --git a/web/app/components/header/github-star/__tests__/index.spec.tsx b/web/app/components/header/github-star/__tests__/index.spec.tsx index 1790f31542..b800622137 100644 --- a/web/app/components/header/github-star/__tests__/index.spec.tsx +++ b/web/app/components/header/github-star/__tests__/index.spec.tsx @@ -1,11 +1,8 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' -import nock from 'nock' -import * as React from 'react' import GithubStar from '../index' -const GITHUB_HOST = 'https://api.github.com' -const GITHUB_PATH = '/repos/langgenius/dify' +const GITHUB_STAR_URL = 'https://ungh.cc/repos/langgenius/dify' const renderWithQueryClient = () => { const queryClient = new QueryClient({ @@ -18,40 +15,66 @@ const renderWithQueryClient = () => { ) } -const mockGithubStar = (status: number, body: Record, delayMs = 0) => { - return nock(GITHUB_HOST).get(GITHUB_PATH).delay(delayMs).reply(status, body) +const createJsonResponse = (body: Record, status = 200) => { + return new Response(JSON.stringify(body), { + status, + headers: { 'Content-Type': 'application/json' }, + }) +} + +const createDeferred = () => { + let resolve!: (value: T | PromiseLike) => void + let reject!: (reason?: unknown) => void + const promise = new Promise((res, rej) => { + resolve = res + reject = rej + }) + + return { promise, resolve, reject } } describe('GithubStar', () => { beforeEach(() => { - nock.cleanAll() + vi.restoreAllMocks() + vi.clearAllMocks() }) - // Shows fetched star count when request succeeds + // Covers the fetched star count shown after a successful request. it('should render fetched star count', async () => { - mockGithubStar(200, { stargazers_count: 123456 }) + const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue( + createJsonResponse({ repo: { stars: 123456 } }), + ) renderWithQueryClient() expect(await screen.findByText('123,456')).toBeInTheDocument() + expect(fetchSpy).toHaveBeenCalledWith(GITHUB_STAR_URL) }) - // Falls back to default star count when request fails + // Covers the fallback star count shown when the request fails. it('should render default star count on error', async () => { - mockGithubStar(500, {}) + vi.spyOn(globalThis, 'fetch').mockResolvedValue( + createJsonResponse({}, 500), + ) renderWithQueryClient() expect(await screen.findByText('110,918')).toBeInTheDocument() }) - // Renders loader while fetching data + // Covers the loading indicator while the fetch promise is still pending. it('should show loader while fetching', async () => { - mockGithubStar(200, { stargazers_count: 222222 }, 50) + const deferred = createDeferred() + vi.spyOn(globalThis, 'fetch').mockReturnValueOnce(deferred.promise) const { container } = renderWithQueryClient() expect(container.querySelector('.animate-spin')).toBeInTheDocument() - await waitFor(() => expect(screen.getByText('222,222')).toBeInTheDocument()) + + deferred.resolve(createJsonResponse({ repo: { stars: 222222 } })) + + await waitFor(() => { + expect(screen.getByText('222,222')).toBeInTheDocument() + }) }) }) diff --git a/web/app/components/header/github-star/index.tsx b/web/app/components/header/github-star/index.tsx index e91bdcca2c..44e8c5ac6f 100644 --- a/web/app/components/header/github-star/index.tsx +++ b/web/app/components/header/github-star/index.tsx @@ -1,16 +1,19 @@ 'use client' import type { FC } from 'react' -import type { GithubRepo } from '@/models/common' -import { RiLoader2Line } from '@remixicon/react' import { useQuery } from '@tanstack/react-query' -import { IS_DEV } from '@/config' -const defaultData = { - stargazers_count: 110918, +type GithubStarResponse = { + repo: { + stars: number + } +} + +const defaultData: GithubStarResponse = { + repo: { stars: 110918 }, } const getStar = async () => { - const res = await fetch('https://api.github.com/repos/langgenius/dify') + const res = await fetch('https://ungh.cc/repos/langgenius/dify') if (!res.ok) throw new Error('Failed to fetch github star') @@ -19,21 +22,20 @@ const getStar = async () => { } const GithubStar: FC<{ className: string }> = (props) => { - const { isFetching, isError, data } = useQuery({ + const { isFetching, isError, data } = useQuery({ queryKey: ['github-star'], queryFn: getStar, - enabled: !IS_DEV, retry: false, placeholderData: defaultData, }) if (isFetching) - return + return if (isError) - return {defaultData.stargazers_count.toLocaleString()} + return {defaultData.repo.stars.toLocaleString()} - return {data?.stargazers_count.toLocaleString()} + return {data?.repo.stars.toLocaleString()} } export default GithubStar diff --git a/web/app/components/plugins/install-plugin/__tests__/hooks.spec.ts b/web/app/components/plugins/install-plugin/__tests__/hooks.spec.ts index 6b0fc27adf..b4171de7f0 100644 --- a/web/app/components/plugins/install-plugin/__tests__/hooks.spec.ts +++ b/web/app/components/plugins/install-plugin/__tests__/hooks.spec.ts @@ -1,6 +1,5 @@ -import { renderHook } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { useGitHubReleases, useGitHubUpload } from '../hooks' +import { checkForUpdates, fetchReleases, handleUpload } from '../hooks' const mockNotify = vi.fn() vi.mock('@/app/components/base/ui/toast', () => ({ @@ -15,10 +14,6 @@ vi.mock('@/app/components/base/ui/toast', () => ({ }), })) -vi.mock('@/config', () => ({ - GITHUB_ACCESS_TOKEN: '', -})) - const mockUploadGitHub = vi.fn() vi.mock('@/service/plugins', () => ({ uploadGitHub: (...args: unknown[]) => mockUploadGitHub(...args), @@ -37,17 +32,17 @@ describe('install-plugin/hooks', () => { it('fetches releases from GitHub API and formats them', async () => { mockFetch.mockResolvedValue({ ok: true, - json: () => Promise.resolve([ - { - tag_name: 'v1.0.0', - assets: [{ browser_download_url: 'https://example.com/v1.zip', name: 'plugin.zip' }], - body: 'Release notes', - }, - ]), + json: () => Promise.resolve({ + releases: [ + { + tag: 'v1.0.0', + assets: [{ downloadUrl: 'https://example.com/plugin.zip' }], + }, + ], + }), }) - const { result } = renderHook(() => useGitHubReleases()) - const releases = await result.current.fetchReleases('owner', 'repo') + const releases = await fetchReleases('owner', 'repo') expect(releases).toHaveLength(1) expect(releases[0].tag_name).toBe('v1.0.0') @@ -60,8 +55,7 @@ describe('install-plugin/hooks', () => { ok: false, }) - const { result } = renderHook(() => useGitHubReleases()) - const releases = await result.current.fetchReleases('owner', 'repo') + const releases = await fetchReleases('owner', 'repo') expect(releases).toEqual([]) expect(mockNotify).toHaveBeenCalledWith('Failed to fetch repository releases') @@ -70,29 +64,26 @@ describe('install-plugin/hooks', () => { describe('checkForUpdates', () => { it('detects newer version available', () => { - const { result } = renderHook(() => useGitHubReleases()) const releases = [ { tag_name: 'v1.0.0', assets: [] }, { tag_name: 'v2.0.0', assets: [] }, ] - const { needUpdate, toastProps } = result.current.checkForUpdates(releases, 'v1.0.0') + const { needUpdate, toastProps } = checkForUpdates(releases, 'v1.0.0') expect(needUpdate).toBe(true) expect(toastProps.message).toContain('v2.0.0') }) it('returns no update when current is latest', () => { - const { result } = renderHook(() => useGitHubReleases()) const releases = [ { tag_name: 'v1.0.0', assets: [] }, ] - const { needUpdate, toastProps } = result.current.checkForUpdates(releases, 'v1.0.0') + const { needUpdate, toastProps } = checkForUpdates(releases, 'v1.0.0') expect(needUpdate).toBe(false) expect(toastProps.type).toBe('info') }) it('returns error for empty releases', () => { - const { result } = renderHook(() => useGitHubReleases()) - const { needUpdate, toastProps } = result.current.checkForUpdates([], 'v1.0.0') + const { needUpdate, toastProps } = checkForUpdates([], 'v1.0.0') expect(needUpdate).toBe(false) expect(toastProps.type).toBe('error') expect(toastProps.message).toContain('empty') @@ -109,8 +100,7 @@ describe('install-plugin/hooks', () => { }) const onSuccess = vi.fn() - const { result } = renderHook(() => useGitHubUpload()) - const pkg = await result.current.handleUpload( + const pkg = await handleUpload( 'https://github.com/owner/repo', 'v1.0.0', 'plugin.difypkg', @@ -132,9 +122,8 @@ describe('install-plugin/hooks', () => { it('shows toast on upload error', async () => { mockUploadGitHub.mockRejectedValue(new Error('Upload failed')) - const { result } = renderHook(() => useGitHubUpload()) await expect( - result.current.handleUpload('url', 'v1', 'pkg'), + handleUpload('url', 'v1', 'pkg'), ).rejects.toThrow('Upload failed') expect(mockNotify).toHaveBeenCalledWith('Error uploading package') }) diff --git a/web/app/components/plugins/install-plugin/hooks.ts b/web/app/components/plugins/install-plugin/hooks.ts index cc7148cc17..f86e6ad672 100644 --- a/web/app/components/plugins/install-plugin/hooks.ts +++ b/web/app/components/plugins/install-plugin/hooks.ts @@ -1,101 +1,87 @@ import type { GitHubRepoReleaseResponse } from '../types' import { toast } from '@/app/components/base/ui/toast' -import { GITHUB_ACCESS_TOKEN } from '@/config' import { uploadGitHub } from '@/service/plugins' import { compareVersion, getLatestVersion } from '@/utils/semver' +const normalizeAssetName = (downloadUrl: string) => { + const parts = downloadUrl.split('/') + return parts[parts.length - 1] +} + const formatReleases = (releases: any) => { return releases.map((release: any) => ({ - tag_name: release.tag_name, + tag_name: release.tag, assets: release.assets.map((asset: any) => ({ - browser_download_url: asset.browser_download_url, - name: asset.name, + browser_download_url: asset.downloadUrl, + name: normalizeAssetName(asset.downloadUrl), })), })) } -export const useGitHubReleases = () => { - const fetchReleases = async (owner: string, repo: string) => { - try { - if (!GITHUB_ACCESS_TOKEN) { - // Fetch releases without authentication from client - const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases`) - if (!res.ok) - throw new Error('Failed to fetch repository releases') - const data = await res.json() - return formatReleases(data) - } - else { - // Fetch releases with authentication from server - const res = await fetch(`/repos/${owner}/${repo}/releases`) - const bodyJson = await res.json() - if (bodyJson.status !== 200) - throw new Error(bodyJson.data.message) - return formatReleases(bodyJson.data) - } - } - catch (error) { - if (error instanceof Error) { - toast.error(error.message) - } - else { - toast.error('Failed to fetch repository releases') - } - return [] - } +export const fetchReleases = async (owner: string, repo: string) => { + try { + // Fetch releases without authentication from client + const res = await fetch(`https://ungh.cc/repos/${owner}/${repo}/releases`) + if (!res.ok) + throw new Error('Failed to fetch repository releases') + const data = await res.json() + return formatReleases(data.releases) } + catch (error) { + if (error instanceof Error) { + toast.error(error.message) + } + else { + toast.error('Failed to fetch repository releases') + } + return [] + } +} - const checkForUpdates = (fetchedReleases: GitHubRepoReleaseResponse[], currentVersion: string) => { - let needUpdate = false - const toastProps: { type?: 'success' | 'error' | 'info' | 'warning', message: string } = { - type: 'info', - message: 'No new version available', - } - if (fetchedReleases.length === 0) { - toastProps.type = 'error' - toastProps.message = 'Input releases is empty' - return { needUpdate, toastProps } - } - const versions = fetchedReleases.map(release => release.tag_name) - const latestVersion = getLatestVersion(versions) - try { - needUpdate = compareVersion(latestVersion, currentVersion) === 1 - if (needUpdate) - toastProps.message = `New version available: ${latestVersion}` - } - catch { - needUpdate = false - toastProps.type = 'error' - toastProps.message = 'Fail to compare versions, please check the version format' - } +export const checkForUpdates = (fetchedReleases: GitHubRepoReleaseResponse[], currentVersion: string) => { + let needUpdate = false + const toastProps: { type?: 'success' | 'error' | 'info' | 'warning', message: string } = { + type: 'info', + message: 'No new version available', + } + if (fetchedReleases.length === 0) { + toastProps.type = 'error' + toastProps.message = 'Input releases is empty' return { needUpdate, toastProps } } - - return { fetchReleases, checkForUpdates } -} - -export const useGitHubUpload = () => { - const handleUpload = async ( - repoUrl: string, - selectedVersion: string, - selectedPackage: string, - onSuccess?: (GitHubPackage: { manifest: any, unique_identifier: string }) => void, - ) => { - try { - const response = await uploadGitHub(repoUrl, selectedVersion, selectedPackage) - const GitHubPackage = { - manifest: response.manifest, - unique_identifier: response.unique_identifier, - } - if (onSuccess) - onSuccess(GitHubPackage) - return GitHubPackage - } - catch (error) { - toast.error('Error uploading package') - throw error - } + const versions = fetchedReleases.map(release => release.tag_name) + const latestVersion = getLatestVersion(versions) + try { + needUpdate = compareVersion(latestVersion, currentVersion) === 1 + if (needUpdate) + toastProps.message = `New version available: ${latestVersion}` + } + catch { + needUpdate = false + toastProps.type = 'error' + toastProps.message = 'Fail to compare versions, please check the version format' + } + return { needUpdate, toastProps } +} + +export const handleUpload = async ( + repoUrl: string, + selectedVersion: string, + selectedPackage: string, + onSuccess?: (GitHubPackage: { manifest: any, unique_identifier: string }) => void, +) => { + try { + const response = await uploadGitHub(repoUrl, selectedVersion, selectedPackage) + const GitHubPackage = { + manifest: response.manifest, + unique_identifier: response.unique_identifier, + } + if (onSuccess) + onSuccess(GitHubPackage) + return GitHubPackage + } + catch (error) { + toast.error('Error uploading package') + throw error } - - return { handleUpload } } diff --git a/web/app/components/plugins/install-plugin/install-from-github/__tests__/index.spec.tsx b/web/app/components/plugins/install-plugin/install-from-github/__tests__/index.spec.tsx index 8abec7817b..2f35f484f0 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/__tests__/index.spec.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/__tests__/index.spec.tsx @@ -74,10 +74,16 @@ vi.mock('@/app/components/plugins/install-plugin/base/use-get-icon', () => ({ default: () => ({ getIconUrl: mockGetIconUrl }), })) -const mockFetchReleases = vi.fn() -vi.mock('../../hooks', () => ({ - useGitHubReleases: () => ({ fetchReleases: mockFetchReleases }), +const { mockFetchReleases } = vi.hoisted(() => ({ + mockFetchReleases: vi.fn(), })) +vi.mock('../../hooks', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + fetchReleases: mockFetchReleases, + } +}) const mockRefreshPluginList = vi.fn() vi.mock('../../hooks/use-refresh-plugin-list', () => ({ diff --git a/web/app/components/plugins/install-plugin/install-from-github/index.tsx b/web/app/components/plugins/install-plugin/install-from-github/index.tsx index ff51698478..c7ace3d94f 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/index.tsx @@ -12,7 +12,7 @@ import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-ico import { cn } from '@/utils/classnames' import { InstallStepFromGitHub } from '../../types' import Installed from '../base/installed' -import { useGitHubReleases } from '../hooks' +import { fetchReleases } from '../hooks' import useHideLogic from '../hooks/use-hide-logic' import useRefreshPluginList from '../hooks/use-refresh-plugin-list' import { convertRepoToUrl, parseGitHubUrl } from '../utils' @@ -31,7 +31,6 @@ type InstallFromGitHubProps = { const InstallFromGitHub: React.FC = ({ updatePayload, onClose, onSuccess }) => { const { t } = useTranslation() const { getIconUrl } = useGetIcon() - const { fetchReleases } = useGitHubReleases() const { refreshPluginList } = useRefreshPluginList() const { diff --git a/web/app/components/plugins/install-plugin/install-from-github/steps/__tests__/selectPackage.spec.tsx b/web/app/components/plugins/install-plugin/install-from-github/steps/__tests__/selectPackage.spec.tsx index 060a5c92a1..cd40f93339 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/steps/__tests__/selectPackage.spec.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/steps/__tests__/selectPackage.spec.tsx @@ -5,11 +5,17 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { PluginCategoryEnum } from '../../../../types' import SelectPackage from '../selectPackage' -// Mock the useGitHubUpload hook -const mockHandleUpload = vi.fn() -vi.mock('../../../hooks', () => ({ - useGitHubUpload: () => ({ handleUpload: mockHandleUpload }), +// Mock upload helper from hooks module +const { mockHandleUpload } = vi.hoisted(() => ({ + mockHandleUpload: vi.fn(), })) +vi.mock('../../../hooks', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + handleUpload: mockHandleUpload, + } +}) // Factory functions const createMockManifest = (): PluginDeclaration => ({ diff --git a/web/app/components/plugins/install-plugin/install-from-github/steps/selectPackage.tsx b/web/app/components/plugins/install-plugin/install-from-github/steps/selectPackage.tsx index 40e32e6c3b..94339eaa97 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/steps/selectPackage.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/steps/selectPackage.tsx @@ -6,7 +6,7 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { PortalSelect } from '@/app/components/base/select' -import { useGitHubUpload } from '../../hooks' +import { handleUpload } from '../../hooks' const i18nPrefix = 'installFromGitHub' @@ -43,7 +43,6 @@ const SelectPackage: React.FC = ({ const { t } = useTranslation() const isEdit = Boolean(updatePayload) const [isUploading, setIsUploading] = React.useState(false) - const { handleUpload } = useGitHubUpload() const handleUploadPackage = async () => { if (isUploading) diff --git a/web/app/components/plugins/plugin-detail-panel/__tests__/detail-header.spec.tsx b/web/app/components/plugins/plugin-detail-panel/__tests__/detail-header.spec.tsx index f8d6488128..e0aa13948b 100644 --- a/web/app/components/plugins/plugin-detail-panel/__tests__/detail-header.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/__tests__/detail-header.spec.tsx @@ -103,12 +103,14 @@ vi.mock('@/service/use-tools', () => ({ useInvalidateAllToolProviders: () => mockInvalidateAllToolProviders, })) -vi.mock('../../install-plugin/hooks', () => ({ - useGitHubReleases: () => ({ +vi.mock('../../install-plugin/hooks', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, checkForUpdates: mockCheckForUpdates, fetchReleases: mockFetchReleases, - }), -})) + } +}) // Auto upgrade settings mock let mockAutoUpgradeInfo: { diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/__tests__/use-plugin-operations.spec.ts b/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/__tests__/use-plugin-operations.spec.ts index 77d41c5bce..b8873f1087 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/__tests__/use-plugin-operations.spec.ts +++ b/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/__tests__/use-plugin-operations.spec.ts @@ -72,12 +72,14 @@ vi.mock('@/service/use-tools', () => ({ useInvalidateAllToolProviders: () => mockInvalidateAllToolProviders, })) -vi.mock('../../../../install-plugin/hooks', () => ({ - useGitHubReleases: () => ({ +vi.mock('../../../../install-plugin/hooks', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, checkForUpdates: mockCheckForUpdates, fetchReleases: mockFetchReleases, - }), -})) + } +}) const createPluginDetail = (overrides: Partial = {}): PluginDetail => ({ id: 'test-id', diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-plugin-operations.ts b/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-plugin-operations.ts index ade47cec5f..765c0e8a4e 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-plugin-operations.ts +++ b/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-plugin-operations.ts @@ -11,7 +11,7 @@ import { useProviderContext } from '@/context/provider-context' import { uninstallPlugin } from '@/service/plugins' import { useInvalidateCheckInstalled } from '@/service/use-plugins' import { useInvalidateAllToolProviders } from '@/service/use-tools' -import { useGitHubReleases } from '../../../install-plugin/hooks' +import { checkForUpdates, fetchReleases } from '../../../install-plugin/hooks' import { PluginCategoryEnum, PluginSource } from '../../../types' type UsePluginOperationsParams = { @@ -39,7 +39,6 @@ export const usePluginOperations = ({ onUpdate, }: UsePluginOperationsParams): UsePluginOperationsReturn => { const { t } = useTranslation() - const { checkForUpdates, fetchReleases } = useGitHubReleases() const { setShowUpdatePluginModal } = useModalContext() const { refreshModelProviders } = useProviderContext() const invalidateCheckInstalled = useInvalidateCheckInstalled() diff --git a/web/app/components/plugins/plugin-item/__tests__/action.spec.tsx b/web/app/components/plugins/plugin-item/__tests__/action.spec.tsx index b4d21c9403..45f5deba22 100644 --- a/web/app/components/plugins/plugin-item/__tests__/action.spec.tsx +++ b/web/app/components/plugins/plugin-item/__tests__/action.spec.tsx @@ -46,13 +46,15 @@ vi.mock('@/service/plugins', () => ({ uninstallPlugin: (id: string) => mockUninstallPlugin(id), })) -// Mock GitHub releases hook -vi.mock('../../install-plugin/hooks', () => ({ - useGitHubReleases: () => ({ +// Mock GitHub release helpers +vi.mock('../../install-plugin/hooks', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, fetchReleases: mockFetchReleases, checkForUpdates: mockCheckForUpdates, - }), -})) + } +}) // Mock modal context vi.mock('@/context/modal-context', () => ({ diff --git a/web/app/components/plugins/plugin-item/action.tsx b/web/app/components/plugins/plugin-item/action.tsx index 413b41e895..c01be54442 100644 --- a/web/app/components/plugins/plugin-item/action.tsx +++ b/web/app/components/plugins/plugin-item/action.tsx @@ -14,7 +14,7 @@ import { useInvalidateInstalledPluginList } from '@/service/use-plugins' import ActionButton from '../../base/action-button' import Confirm from '../../base/confirm' import Tooltip from '../../base/tooltip' -import { useGitHubReleases } from '../install-plugin/hooks' +import { checkForUpdates, fetchReleases } from '../install-plugin/hooks' import PluginInfo from '../plugin-page/plugin-info' import { PluginSource } from '../types' @@ -54,7 +54,6 @@ const Action: FC = ({ setTrue: showDeleting, setFalse: hideDeleting, }] = useBoolean(false) - const { checkForUpdates, fetchReleases } = useGitHubReleases() const { setShowUpdatePluginModal } = useModalContext() const invalidateInstalledPluginList = useInvalidateInstalledPluginList() diff --git a/web/app/repos/[owner]/[repo]/releases/route.ts b/web/app/repos/[owner]/[repo]/releases/route.ts deleted file mode 100644 index 1ae8a5327f..0000000000 --- a/web/app/repos/[owner]/[repo]/releases/route.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { NextRequest } from '@/next/server' -import { Octokit } from '@octokit/core' -import { RequestError } from '@octokit/request-error' -import { GITHUB_ACCESS_TOKEN } from '@/config' -import { NextResponse } from '@/next/server' - -type Params = { - owner: string - repo: string -} - -const octokit = new Octokit({ - auth: GITHUB_ACCESS_TOKEN, -}) - -export async function GET( - request: NextRequest, - { params }: { params: Promise }, -) { - const { owner, repo } = (await params) - try { - const releasesRes = await octokit.request('GET /repos/{owner}/{repo}/releases', { - owner, - repo, - headers: { - 'X-GitHub-Api-Version': '2022-11-28', - }, - }) - return NextResponse.json(releasesRes) - } - catch (error) { - if (error instanceof RequestError) - return NextResponse.json(error.response) - else - throw error - } -} diff --git a/web/config/index.ts b/web/config/index.ts index eed914726c..999aa754e6 100644 --- a/web/config/index.ts +++ b/web/config/index.ts @@ -292,9 +292,6 @@ export const resetHITLInputReg = () => HITL_INPUT_REG.lastIndex = 0 export const DISABLE_UPLOAD_IMAGE_AS_ICON = env.NEXT_PUBLIC_DISABLE_UPLOAD_IMAGE_AS_ICON -export const GITHUB_ACCESS_TOKEN - = env.NEXT_PUBLIC_GITHUB_ACCESS_TOKEN - export const SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS = '.difypkg,.difybndl' export const FULL_DOC_PREVIEW_LENGTH = 50 diff --git a/web/docs/test.md b/web/docs/test.md index 2facdbb29f..cb22b73b15 100644 --- a/web/docs/test.md +++ b/web/docs/test.md @@ -283,16 +283,6 @@ Reserve snapshots for static, deterministic fragments (icons, badges, layout chr **Note**: Dify is a desktop application. **No need for** responsive/mobile testing. -### 12. Mock API - -Use Nock to mock API calls. Example: - -```ts -const mockGithubStar = (status: number, body: Record, delayMs = 0) => { - return nock(GITHUB_HOST).get(GITHUB_PATH).delay(delayMs).reply(status, body) -} -``` - ## Code Style ### Example Structure diff --git a/web/env.ts b/web/env.ts index 8ecde76143..55709918a8 100644 --- a/web/env.ts +++ b/web/env.ts @@ -66,10 +66,6 @@ const clientSchema = { NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL: coercedBoolean.default(true), NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER: coercedBoolean.default(true), NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL: coercedBoolean.default(false), - /** - * Github Access Token, used for invoking Github API - */ - NEXT_PUBLIC_GITHUB_ACCESS_TOKEN: z.string().optional(), /** * The maximum number of tokens for segmentation */ @@ -171,7 +167,6 @@ export const env = createEnv({ NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL: isServer ? process.env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL : getRuntimeEnvFromBody('enableWebsiteFirecrawl'), NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER: isServer ? process.env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER : getRuntimeEnvFromBody('enableWebsiteJinareader'), NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL: isServer ? process.env.NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL : getRuntimeEnvFromBody('enableWebsiteWatercrawl'), - NEXT_PUBLIC_GITHUB_ACCESS_TOKEN: isServer ? process.env.NEXT_PUBLIC_GITHUB_ACCESS_TOKEN : getRuntimeEnvFromBody('githubAccessToken'), NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: isServer ? process.env.NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH : getRuntimeEnvFromBody('indexingMaxSegmentationTokensLength'), NEXT_PUBLIC_IS_MARKETPLACE: isServer ? process.env.NEXT_PUBLIC_IS_MARKETPLACE : getRuntimeEnvFromBody('isMarketplace'), NEXT_PUBLIC_LOOP_NODE_MAX_COUNT: isServer ? process.env.NEXT_PUBLIC_LOOP_NODE_MAX_COUNT : getRuntimeEnvFromBody('loopNodeMaxCount'), diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index 3d311dbf54..cb00a65732 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -1937,6 +1937,11 @@ "count": 4 } }, + "app/components/base/date-and-time-picker/hooks.ts": { + "react/no-unnecessary-use-prefix": { + "count": 2 + } + }, "app/components/base/date-and-time-picker/time-picker/header.tsx": { "tailwindcss/enforce-consistent-class-order": { "count": 1 @@ -2184,6 +2189,9 @@ "no-restricted-imports": { "count": 1 }, + "react/no-unnecessary-use-prefix": { + "count": 1 + }, "ts/no-explicit-any": { "count": 3 } @@ -4766,6 +4774,9 @@ } }, "app/components/header/account-setting/model-provider-page/hooks.ts": { + "react/no-unnecessary-use-prefix": { + "count": 1 + }, "ts/no-explicit-any": { "count": 2 } @@ -4819,6 +4830,11 @@ "count": 6 } }, + "app/components/header/account-setting/model-provider-page/model-auth/hooks/use-custom-models.ts": { + "react/no-unnecessary-use-prefix": { + "count": 2 + } + }, "app/components/header/account-setting/model-provider-page/model-auth/hooks/use-model-form-schemas.ts": { "ts/no-explicit-any": { "count": 2 @@ -5038,6 +5054,11 @@ "count": 4 } }, + "app/components/plugins/install-plugin/hooks/use-fold-anim-into.ts": { + "react/no-unnecessary-use-prefix": { + "count": 1 + } + }, "app/components/plugins/install-plugin/install-bundle/index.tsx": { "erasable-syntax-only/enums": { "count": 1 @@ -5242,6 +5263,11 @@ "count": 1 } }, + "app/components/plugins/plugin-auth/hooks/use-get-api.ts": { + "react/no-unnecessary-use-prefix": { + "count": 1 + } + }, "app/components/plugins/plugin-auth/hooks/use-plugin-auth-action.ts": { "no-restricted-imports": { "count": 1 @@ -7229,6 +7255,11 @@ "count": 2 } }, + "app/components/workflow/nodes/_base/components/variable/variable-label/hooks.ts": { + "react/no-unnecessary-use-prefix": { + "count": 2 + } + }, "app/components/workflow/nodes/_base/components/workflow-panel/index.tsx": { "no-restricted-imports": { "count": 1 @@ -7260,6 +7291,9 @@ } }, "app/components/workflow/nodes/_base/components/workflow-panel/last-run/use-last-run.ts": { + "react/no-unnecessary-use-prefix": { + "count": 2 + }, "react/set-state-in-effect": { "count": 1 }, @@ -9827,6 +9861,11 @@ "count": 7 } }, + "service/use-flow.ts": { + "react/no-unnecessary-use-prefix": { + "count": 1 + } + }, "service/use-pipeline.ts": { "@tanstack/query/exhaustive-deps": { "count": 2 diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index d5d833ba69..fdbd1d018c 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -66,12 +66,14 @@ export default antfu( ...pluginReact.configs['recommended-typescript'].rules, 'react/prefer-namespace-import': 'error', 'react/set-state-in-effect': 'error', + 'react/no-unnecessary-use-prefix': 'error', }, }, { files: [...GLOB_TESTS, GLOB_MARKDOWN_CODE, 'vitest.setup.ts', 'test/i18n-mock.ts'], rules: { 'react/component-hook-factories': 'off', + 'react/no-unnecessary-use-prefix': 'off', }, }, reactRefresh.configs.next(), diff --git a/web/models/common.ts b/web/models/common.ts index 62a543672b..9c8866b25b 100644 --- a/web/models/common.ts +++ b/web/models/common.ts @@ -220,10 +220,6 @@ export type DataSources = { sources: DataSourceItem[] } -export type GithubRepo = { - stargazers_count: number -} - export type PluginProvider = { tool_name: string is_enabled: boolean diff --git a/web/next/server.ts b/web/next/server.ts deleted file mode 100644 index 037538be96..0000000000 --- a/web/next/server.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { NextResponse } from 'next/server' -export type { NextRequest } from 'next/server' diff --git a/web/package.json b/web/package.json index a0f0bc26df..765c56efd4 100644 --- a/web/package.json +++ b/web/package.json @@ -74,8 +74,6 @@ "@lexical/text": "0.42.0", "@lexical/utils": "0.42.0", "@monaco-editor/react": "4.7.0", - "@octokit/core": "7.0.6", - "@octokit/request-error": "7.1.0", "@orpc/client": "1.13.9", "@orpc/contract": "1.13.9", "@orpc/openapi-client": "1.13.9", @@ -228,7 +226,6 @@ "jsdom-testing-mocks": "1.16.0", "knip": "6.0.2", "lint-staged": "16.4.0", - "nock": "14.0.11", "postcss": "8.5.8", "postcss-js": "5.1.0", "react-server-dom-webpack": "19.2.4", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 9945c6f893..2801e07df8 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -109,12 +109,6 @@ importers: '@monaco-editor/react': specifier: 4.7.0 version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@octokit/core': - specifier: 7.0.6 - version: 7.0.6 - '@octokit/request-error': - specifier: 7.1.0 - version: 7.1.0 '@orpc/client': specifier: 1.13.9 version: 1.13.9 @@ -566,9 +560,6 @@ importers: lint-staged: specifier: 16.4.0 version: 16.4.0 - nock: - specifier: 14.0.11 - version: 14.0.11 postcss: specifier: 8.5.8 version: 8.5.8 @@ -1685,10 +1676,6 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@mswjs/interceptors@0.41.3': - resolution: {integrity: sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA==} - engines: {node: '>=18'} - '@napi-rs/wasm-runtime@1.1.1': resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} @@ -1791,45 +1778,6 @@ packages: resolution: {integrity: sha512-y3SvzjuY1ygnzWA4Krwx/WaJAsTMP11DN+e21A8Fa8PW1oDtVB5NSRW7LWurAiS2oKRkuCgcjTYMkBuBkcPCRg==} engines: {node: '>=12.4.0'} - '@octokit/auth-token@6.0.0': - resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} - engines: {node: '>= 20'} - - '@octokit/core@7.0.6': - resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} - engines: {node: '>= 20'} - - '@octokit/endpoint@11.0.3': - resolution: {integrity: sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==} - engines: {node: '>= 20'} - - '@octokit/graphql@9.0.3': - resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} - engines: {node: '>= 20'} - - '@octokit/openapi-types@27.0.0': - resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} - - '@octokit/request-error@7.1.0': - resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} - engines: {node: '>= 20'} - - '@octokit/request@10.0.8': - resolution: {integrity: sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw==} - engines: {node: '>= 20'} - - '@octokit/types@16.0.0': - resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} - - '@open-draft/deferred-promise@2.2.0': - resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} - - '@open-draft/logger@0.3.0': - resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} - - '@open-draft/until@2.1.0': - resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@orpc/client@1.13.9': resolution: {integrity: sha512-RmD2HDgmGgF6zgHHdybE4zH6QJoHjC+/C3n56yLf+fmWbiZtwnOUETgGCroY6S8aK2fpy6hJ3wZaJUjfWVuGHg==} @@ -4127,9 +4075,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - before-after-hook@4.0.0: - resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} - bezier-easing@2.1.0: resolution: {integrity: sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==} @@ -5223,9 +5168,6 @@ packages: engines: {node: '>= 10.17.0'} hasBin: true - fast-content-type-parse@3.0.0: - resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -5645,9 +5587,6 @@ packages: engines: {node: '>=14.16'} hasBin: true - is-node-process@1.2.0: - resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -5774,12 +5713,6 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - - json-with-bigint@3.5.7: - resolution: {integrity: sha512-7ei3MdAI5+fJPVnKlW77TKNKwQ5ppSzWvhPuSuINT/GYW9ZOC1eRKOuhV9yHG5aEsUPj9BBx5JIekkmoLHxZOw==} - json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -6359,10 +6292,6 @@ packages: sass: optional: true - nock@14.0.11: - resolution: {integrity: sha512-u5xUnYE+UOOBA6SpELJheMCtj2Laqx15Vl70QxKo43Wz/6nMHXS7PrEioXLjXAwhmawdEMNImwKCcPhBJWbKVw==} - engines: {node: '>=18.20.0 <20 || >=20.12.1'} - node-abi@3.89.0: resolution: {integrity: sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==} engines: {node: '>=10'} @@ -6445,9 +6374,6 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - outvariant@1.4.3: - resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} - oxc-parser@0.120.0: resolution: {integrity: sha512-WyPWZlcIm+Fkte63FGfgFB8mAAk33aH9h5N9lphXVOHSXEBFFsmYdOBedVKly363aWABjZdaj/m9lBfEY4wt+w==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6710,10 +6636,6 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - propagate@2.0.1: - resolution: {integrity: sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==} - engines: {node: '>= 8'} - property-information@5.6.0: resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==} @@ -7251,9 +7173,6 @@ packages: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - strict-event-emitter@0.5.1: - resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} - string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -7640,9 +7559,6 @@ packages: unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} - universal-user-agent@7.0.3: - resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} - universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -9325,15 +9241,6 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@mswjs/interceptors@0.41.3': - dependencies: - '@open-draft/deferred-promise': 2.2.0 - '@open-draft/logger': 0.3.0 - '@open-draft/until': 2.1.0 - is-node-process: 1.2.0 - outvariant: 1.4.3 - strict-event-emitter: 0.5.1 - '@napi-rs/wasm-runtime@1.1.1': dependencies: '@emnapi/core': 1.9.0 @@ -9400,57 +9307,6 @@ snapshots: '@nolyfill/side-channel@1.0.44': {} - '@octokit/auth-token@6.0.0': {} - - '@octokit/core@7.0.6': - dependencies: - '@octokit/auth-token': 6.0.0 - '@octokit/graphql': 9.0.3 - '@octokit/request': 10.0.8 - '@octokit/request-error': 7.1.0 - '@octokit/types': 16.0.0 - before-after-hook: 4.0.0 - universal-user-agent: 7.0.3 - - '@octokit/endpoint@11.0.3': - dependencies: - '@octokit/types': 16.0.0 - universal-user-agent: 7.0.3 - - '@octokit/graphql@9.0.3': - dependencies: - '@octokit/request': 10.0.8 - '@octokit/types': 16.0.0 - universal-user-agent: 7.0.3 - - '@octokit/openapi-types@27.0.0': {} - - '@octokit/request-error@7.1.0': - dependencies: - '@octokit/types': 16.0.0 - - '@octokit/request@10.0.8': - dependencies: - '@octokit/endpoint': 11.0.3 - '@octokit/request-error': 7.1.0 - '@octokit/types': 16.0.0 - fast-content-type-parse: 3.0.0 - json-with-bigint: 3.5.7 - universal-user-agent: 7.0.3 - - '@octokit/types@16.0.0': - dependencies: - '@octokit/openapi-types': 27.0.0 - - '@open-draft/deferred-promise@2.2.0': {} - - '@open-draft/logger@0.3.0': - dependencies: - is-node-process: 1.2.0 - outvariant: 1.4.3 - - '@open-draft/until@2.1.0': {} - '@orpc/client@1.13.9': dependencies: '@orpc/shared': 1.13.9 @@ -11553,8 +11409,6 @@ snapshots: baseline-browser-mapping@2.10.8: {} - before-after-hook@4.0.0: {} - bezier-easing@2.1.0: {} bidi-js@1.0.3: @@ -12873,8 +12727,6 @@ snapshots: transitivePeerDependencies: - supports-color - fast-content-type-parse@3.0.0: {} - fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -13325,8 +13177,6 @@ snapshots: dependencies: is-docker: 3.0.0 - is-node-process@1.2.0: {} - is-number@7.0.0: {} is-plain-obj@4.1.0: {} @@ -13438,10 +13288,6 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} - json-stringify-safe@5.0.1: {} - - json-with-bigint@3.5.7: {} - json5@2.2.3: {} jsonc-eslint-parser@3.1.0: @@ -14326,12 +14172,6 @@ snapshots: - '@babel/core' - babel-plugin-macros - nock@14.0.11: - dependencies: - '@mswjs/interceptors': 0.41.3 - json-stringify-safe: 5.0.1 - propagate: 2.0.1 - node-abi@3.89.0: dependencies: semver: 7.7.4 @@ -14401,8 +14241,6 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - outvariant@1.4.3: {} - oxc-parser@0.120.0: dependencies: '@oxc-project/types': 0.120.0 @@ -14752,8 +14590,6 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 - propagate@2.0.1: {} - property-information@5.6.0: dependencies: xtend: 4.0.2 @@ -15452,8 +15288,6 @@ snapshots: transitivePeerDependencies: - supports-color - strict-event-emitter@0.5.1: {} - string-argv@0.3.2: {} string-ts@2.3.1: {} @@ -15857,8 +15691,6 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - universal-user-agent@7.0.3: {} - universalify@2.0.1: {} unpic@4.2.2: {} diff --git a/web/proxy.ts b/web/proxy.ts index 02513d91b9..af9b290025 100644 --- a/web/proxy.ts +++ b/web/proxy.ts @@ -1,9 +1,11 @@ -import type { NextRequest } from '@/next/server' +// eslint-disable-next-line no-restricted-imports +import type { NextRequest } from 'next/server' import { Buffer } from 'node:buffer' +// eslint-disable-next-line no-restricted-imports +import { NextResponse } from 'next/server' import { env } from '@/env' -import { NextResponse } from '@/next/server' -const NECESSARY_DOMAIN = '*.sentry.io http://localhost:* http://127.0.0.1:* https://analytics.google.com googletagmanager.com *.googletagmanager.com https://www.google-analytics.com https://api.github.com https://api2.amplitude.com *.amplitude.com' +const NECESSARY_DOMAIN = '*.sentry.io http://localhost:* http://127.0.0.1:* https://analytics.google.com googletagmanager.com *.googletagmanager.com https://www.google-analytics.com https://ungh.cc https://api2.amplitude.com *.amplitude.com' const wrapResponseWithXFrameOptions = (response: NextResponse, pathname: string) => { // prevent clickjacking: https://owasp.org/www-community/attacks/Clickjacking