mirror of
https://github.com/langgenius/dify.git
synced 2026-03-27 11:02:18 -04:00
refactor: use ungh for github api (#34108)
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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<typeof vi.fn>).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<typeof vi.fn>).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<typeof vi.fn>).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(
|
||||
|
||||
@@ -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<string, unknown>, delayMs = 0) => {
|
||||
return nock(GITHUB_HOST).get(GITHUB_PATH).delay(delayMs).reply(status, body)
|
||||
const createJsonResponse = (body: Record<string, unknown>, status = 200) => {
|
||||
return new Response(JSON.stringify(body), {
|
||||
status,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
|
||||
const createDeferred = <T,>() => {
|
||||
let resolve!: (value: T | PromiseLike<T>) => void
|
||||
let reject!: (reason?: unknown) => void
|
||||
const promise = new Promise<T>((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<Response>()
|
||||
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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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<GithubRepo>({
|
||||
const { isFetching, isError, data } = useQuery<GithubStarResponse>({
|
||||
queryKey: ['github-star'],
|
||||
queryFn: getStar,
|
||||
enabled: !IS_DEV,
|
||||
retry: false,
|
||||
placeholderData: defaultData,
|
||||
})
|
||||
|
||||
if (isFetching)
|
||||
return <RiLoader2Line className="size-3 shrink-0 animate-spin text-text-tertiary" />
|
||||
return <span className="i-ri-loader-2-line size-3 shrink-0 animate-spin text-text-tertiary" />
|
||||
|
||||
if (isError)
|
||||
return <span {...props}>{defaultData.stargazers_count.toLocaleString()}</span>
|
||||
return <span {...props}>{defaultData.repo.stars.toLocaleString()}</span>
|
||||
|
||||
return <span {...props}>{data?.stargazers_count.toLocaleString()}</span>
|
||||
return <span {...props}>{data?.repo.stars.toLocaleString()}</span>
|
||||
}
|
||||
|
||||
export default GithubStar
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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<typeof import('../../hooks')>()
|
||||
return {
|
||||
...actual,
|
||||
fetchReleases: mockFetchReleases,
|
||||
}
|
||||
})
|
||||
|
||||
const mockRefreshPluginList = vi.fn()
|
||||
vi.mock('../../hooks/use-refresh-plugin-list', () => ({
|
||||
|
||||
@@ -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<InstallFromGitHubProps> = ({ updatePayload, onClose, onSuccess }) => {
|
||||
const { t } = useTranslation()
|
||||
const { getIconUrl } = useGetIcon()
|
||||
const { fetchReleases } = useGitHubReleases()
|
||||
const { refreshPluginList } = useRefreshPluginList()
|
||||
|
||||
const {
|
||||
|
||||
@@ -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<typeof import('../../../hooks')>()
|
||||
return {
|
||||
...actual,
|
||||
handleUpload: mockHandleUpload,
|
||||
}
|
||||
})
|
||||
|
||||
// Factory functions
|
||||
const createMockManifest = (): PluginDeclaration => ({
|
||||
|
||||
@@ -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<SelectPackageProps> = ({
|
||||
const { t } = useTranslation()
|
||||
const isEdit = Boolean(updatePayload)
|
||||
const [isUploading, setIsUploading] = React.useState(false)
|
||||
const { handleUpload } = useGitHubUpload()
|
||||
|
||||
const handleUploadPackage = async () => {
|
||||
if (isUploading)
|
||||
|
||||
@@ -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<typeof import('../../install-plugin/hooks')>()
|
||||
return {
|
||||
...actual,
|
||||
checkForUpdates: mockCheckForUpdates,
|
||||
fetchReleases: mockFetchReleases,
|
||||
}),
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
// Auto upgrade settings mock
|
||||
let mockAutoUpgradeInfo: {
|
||||
|
||||
@@ -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<typeof import('../../../../install-plugin/hooks')>()
|
||||
return {
|
||||
...actual,
|
||||
checkForUpdates: mockCheckForUpdates,
|
||||
fetchReleases: mockFetchReleases,
|
||||
}),
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
const createPluginDetail = (overrides: Partial<PluginDetail> = {}): PluginDetail => ({
|
||||
id: 'test-id',
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<typeof import('../../install-plugin/hooks')>()
|
||||
return {
|
||||
...actual,
|
||||
fetchReleases: mockFetchReleases,
|
||||
checkForUpdates: mockCheckForUpdates,
|
||||
}),
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
// Mock modal context
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
|
||||
@@ -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<Props> = ({
|
||||
setTrue: showDeleting,
|
||||
setFalse: hideDeleting,
|
||||
}] = useBoolean(false)
|
||||
const { checkForUpdates, fetchReleases } = useGitHubReleases()
|
||||
const { setShowUpdatePluginModal } = useModalContext()
|
||||
const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
|
||||
|
||||
|
||||
@@ -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<Params> },
|
||||
) {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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<string, unknown>, delayMs = 0) => {
|
||||
return nock(GITHUB_HOST).get(GITHUB_PATH).delay(delayMs).reply(status, body)
|
||||
}
|
||||
```
|
||||
|
||||
## Code Style
|
||||
|
||||
### Example Structure
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -220,10 +220,6 @@ export type DataSources = {
|
||||
sources: DataSourceItem[]
|
||||
}
|
||||
|
||||
export type GithubRepo = {
|
||||
stargazers_count: number
|
||||
}
|
||||
|
||||
export type PluginProvider = {
|
||||
tool_name: string
|
||||
is_enabled: boolean
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export { NextResponse } from 'next/server'
|
||||
export type { NextRequest } from 'next/server'
|
||||
@@ -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",
|
||||
|
||||
168
web/pnpm-lock.yaml
generated
168
web/pnpm-lock.yaml
generated
@@ -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: {}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user