feat: refactor GitHub releases fetching and update handling with improved error notifications

pull/12372/head
twwu 2 years ago
parent d3fe6fd303
commit 87b23a1fac

@ -1,26 +1,16 @@
import Toast from '@/app/components/base/toast' import Toast, { type IToastProps } from '@/app/components/base/toast'
import { uploadGitHub } from '@/service/plugins' import { uploadGitHub } from '@/service/plugins'
import { Octokit } from '@octokit/core'
import { GITHUB_ACCESS_TOKEN } from '@/config'
import { compareVersion, getLatestVersion } from '@/utils/semver' import { compareVersion, getLatestVersion } from '@/utils/semver'
import type { GitHubRepoReleaseResponse } from '../types' import type { GitHubRepoReleaseResponse } from '../types'
export const useGitHubReleases = () => { export const useGitHubReleases = () => {
const fetchReleases = async (owner: string, repo: string) => { const fetchReleases = async (owner: string, repo: string) => {
try { try {
const octokit = new Octokit({ const res = await fetch(`/repos/${owner}/${repo}/releases`)
auth: GITHUB_ACCESS_TOKEN, const bodyJson = await res.json()
}) if (bodyJson.status !== 200) throw new Error(bodyJson.data.message)
const res = await octokit.request('GET /repos/{owner}/{repo}/releases', {
owner,
repo,
headers: {
'X-GitHub-Api-Version': '2022-11-28',
},
})
if (res.status !== 200) throw new Error('Failed to fetch releases')
const formattedReleases = res.data.map((release: any) => ({ const formattedReleases = bodyJson.data.map((release: any) => ({
tag_name: release.tag_name, tag_name: release.tag_name,
assets: release.assets.map((asset: any) => ({ assets: release.assets.map((asset: any) => ({
browser_download_url: asset.browser_download_url, browser_download_url: asset.browser_download_url,
@ -31,26 +21,46 @@ export const useGitHubReleases = () => {
return formattedReleases return formattedReleases
} }
catch (error) { catch (error) {
Toast.notify({ if (error instanceof Error) {
type: 'error', Toast.notify({
message: 'Failed to fetch repository releases', type: 'error',
}) message: error.message,
})
}
else {
Toast.notify({
type: 'error',
message: 'Failed to fetch repository releases',
})
}
return [] return []
} }
} }
const checkForUpdates = (fetchedReleases: GitHubRepoReleaseResponse[], currentVersion: string) => { const checkForUpdates = (fetchedReleases: GitHubRepoReleaseResponse[], currentVersion: string) => {
if (fetchedReleases.length === 0) throw new Error('No releases found') let needUpdate = false
const toastProps: IToastProps = {
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 versions = fetchedReleases.map(release => release.tag_name)
const latestVersion = getLatestVersion(versions) const latestVersion = getLatestVersion(versions)
let res = false
try { try {
res = compareVersion(latestVersion, currentVersion) === 1 needUpdate = compareVersion(latestVersion, currentVersion) === 1
if (needUpdate)
toastProps.message = `New version available: ${latestVersion}`
} }
catch { catch {
throw new Error('Failed to compare versions, please check the version format.') needUpdate = false
toastProps.type = 'error'
toastProps.message = 'Fail to compare versions, please check the version format'
} }
return res return { needUpdate, toastProps }
} }
return { fetchReleases, checkForUpdates } return { fetchReleases, checkForUpdates }

@ -88,47 +88,28 @@ const DetailHeader = ({
return return
} }
try { const fetchedReleases = await fetchReleases(author, name)
const fetchedReleases = await fetchReleases(author, name) if (fetchedReleases.length === 0) return
if (checkForUpdates(fetchedReleases, meta!.version)) { const { needUpdate, toastProps } = checkForUpdates(fetchedReleases, meta!.version)
setShowUpdatePluginModal({ Toast.notify(toastProps)
onSaveCallback: () => { if (needUpdate) {
onUpdate() setShowUpdatePluginModal({
}, onSaveCallback: () => {
payload: { onUpdate()
type: PluginSource.github, },
github: { payload: {
originalPackageInfo: { type: PluginSource.github,
id: detail.plugin_unique_identifier, github: {
repo: meta!.repo, originalPackageInfo: {
version: meta!.version, id: detail.plugin_unique_identifier,
package: meta!.package, repo: meta!.repo,
releases: fetchedReleases, version: meta!.version,
}, package: meta!.package,
releases: fetchedReleases,
}, },
}, },
}) },
} })
else {
Toast.notify({
type: 'info',
message: 'No new version available',
})
}
}
catch (error) {
if (error instanceof Error) {
Toast.notify({
type: 'error',
message: error.message,
})
}
else {
Toast.notify({
type: 'error',
message: 'Failed to compare versions',
})
}
} }
} }

@ -54,47 +54,28 @@ const Action: FC<Props> = ({
const invalidateInstalledPluginList = useInvalidateInstalledPluginList() const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
const handleFetchNewVersion = async () => { const handleFetchNewVersion = async () => {
try { const fetchedReleases = await fetchReleases(author, pluginName)
const fetchedReleases = await fetchReleases(author, pluginName) if (fetchReleases.length === 0) return
if (checkForUpdates(fetchedReleases, meta!.version)) { const { needUpdate, toastProps } = checkForUpdates(fetchedReleases, meta!.version)
setShowUpdatePluginModal({ Toast.notify(toastProps)
onSaveCallback: () => { if (needUpdate) {
invalidateInstalledPluginList() setShowUpdatePluginModal({
}, onSaveCallback: () => {
payload: { invalidateInstalledPluginList()
type: PluginSource.github, },
github: { payload: {
originalPackageInfo: { type: PluginSource.github,
id: pluginUniqueIdentifier, github: {
repo: meta!.repo, originalPackageInfo: {
version: meta!.version, id: pluginUniqueIdentifier,
package: meta!.package, repo: meta!.repo,
releases: fetchedReleases, version: meta!.version,
}, package: meta!.package,
releases: fetchedReleases,
}, },
}, },
}) },
} })
else {
Toast.notify({
type: 'info',
message: 'No new version available',
})
}
}
catch (error) {
if (error instanceof Error) {
Toast.notify({
type: 'error',
message: error.message,
})
}
else {
Toast.notify({
type: 'error',
message: 'Failed to compare versions',
})
}
} }
} }

@ -0,0 +1,36 @@
import { type NextRequest, NextResponse } from 'next/server'
import { Octokit } from '@octokit/core'
import { RequestError } from '@octokit/request-error'
import { GITHUB_ACCESS_TOKEN } from '@/config'
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
}
}

@ -38,6 +38,7 @@
"@monaco-editor/react": "^4.6.0", "@monaco-editor/react": "^4.6.0",
"@next/mdx": "^14.0.4", "@next/mdx": "^14.0.4",
"@octokit/core": "^6.1.2", "@octokit/core": "^6.1.2",
"@octokit/request-error": "^6.1.5",
"@remixicon/react": "^4.3.0", "@remixicon/react": "^4.3.0",
"@sentry/react": "^7.54.0", "@sentry/react": "^7.54.0",
"@sentry/utils": "^7.54.0", "@sentry/utils": "^7.54.0",

@ -55,6 +55,9 @@ importers:
'@octokit/core': '@octokit/core':
specifier: ^6.1.2 specifier: ^6.1.2
version: 6.1.2 version: 6.1.2
'@octokit/request-error':
specifier: ^6.1.5
version: 6.1.5
'@remixicon/react': '@remixicon/react':
specifier: ^4.3.0 specifier: ^4.3.0
version: 4.3.0(react@18.2.0) version: 4.3.0(react@18.2.0)

Loading…
Cancel
Save