From 43254ceeb062b797b47184fb0cacee3dc81e20cb Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 5 Nov 2024 15:22:48 +0800 Subject: [PATCH 1/4] chore: temp types --- web/app/components/plugins/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index f58d870fe9..386ecf5f7e 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -146,8 +146,8 @@ export type UpdateFromMarketPlacePayload = { export type UpdateFromGitHubPayload = { originalPackageInfo: { + id: string repo: string - originalPluginId: string version: string } } From da15a25cf57facdc0a2efdc45649ac8627896694 Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 5 Nov 2024 15:27:08 +0800 Subject: [PATCH 2/4] feat: add update from github modal content --- web/app/(commonLayout)/plugins/test/update/page.tsx | 11 ++++++++--- .../install-plugin/install-from-github/index.tsx | 1 + .../components/plugins/update-plugin/from-github.tsx | 11 ++++++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/web/app/(commonLayout)/plugins/test/update/page.tsx b/web/app/(commonLayout)/plugins/test/update/page.tsx index cbd6da1d62..0f3d05ea7b 100644 --- a/web/app/(commonLayout)/plugins/test/update/page.tsx +++ b/web/app/(commonLayout)/plugins/test/update/page.tsx @@ -32,9 +32,14 @@ const UpdatePlugin = () => { payload: { type: PluginSource.github, github: { - repo: 'repo_xxx', - originalPluginId: 'original_xxx', - version: 'version_xxx', + originalPackageInfo: { + id: '111', + repo: 'aaa/bbb', + version: 'xxx', + url: 'aaa/bbb', + currVersion: '1.2.3', + currPackage: 'pack1', + } as any, }, }, onCancelCallback: () => { 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 a064cab53e..979622f433 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 @@ -18,6 +18,7 @@ import { useTranslation } from 'react-i18next' type InstallFromGitHubProps = { updatePayload?: UpdateFromGitHubPayload onClose: () => void + onSuccess: () => void } const InstallFromGitHub: React.FC = ({ updatePayload, onClose }) => { diff --git a/web/app/components/plugins/update-plugin/from-github.tsx b/web/app/components/plugins/update-plugin/from-github.tsx index 3d71547c7f..9bc2f2ad3e 100644 --- a/web/app/components/plugins/update-plugin/from-github.tsx +++ b/web/app/components/plugins/update-plugin/from-github.tsx @@ -2,6 +2,7 @@ import type { FC } from 'react' import React from 'react' import type { UpdateFromGitHubPayload } from '../types' +import InstallFromGitHub from '../install-plugin/install-from-github' type Props = { payload: UpdateFromGitHubPayload @@ -11,11 +12,15 @@ type Props = { const FromGitHub: FC = ({ payload, + onSave, + onCancel, }) => { return ( -
- {JSON.stringify(payload)} -
+ ) } export default React.memo(FromGitHub) From e989c1f3aae5478e0e0215fe835cc10877bdf446 Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 5 Nov 2024 16:04:52 +0800 Subject: [PATCH 3/4] feat: fill update install payload --- .../plugins/test/update/page.tsx | 6 +- web/app/components/plugins/card/card-mock.ts | 58 +++++++++++++++++++ .../install-from-local-package/index.tsx | 4 +- .../plugins/install-plugin/utils.ts | 2 + web/app/components/plugins/types.ts | 5 +- .../update-plugin/from-market-place.tsx | 25 ++++++-- 6 files changed, 91 insertions(+), 9 deletions(-) diff --git a/web/app/(commonLayout)/plugins/test/update/page.tsx b/web/app/(commonLayout)/plugins/test/update/page.tsx index 0f3d05ea7b..9d78b45979 100644 --- a/web/app/(commonLayout)/plugins/test/update/page.tsx +++ b/web/app/(commonLayout)/plugins/test/update/page.tsx @@ -1,4 +1,5 @@ 'use client' +import { toolNeko } from '@/app/components/plugins/card/card-mock' import { PluginSource } from '@/app/components/plugins/types' import { useModalContext } from '@/context/modal-context' import React from 'react' @@ -11,11 +12,12 @@ const UpdatePlugin = () => { type: PluginSource.marketplace, marketPlace: { originalPackageInfo: { - id: 'original_xxx', + id: 'langgenius/neko:0.0.1@9e57d693739287c0efdc96847d7ed959ca93f70aa704471f2eb7ed3313821824', + payload: toolNeko as any, }, targetPackageInfo: { id: 'target_xxx', - payload: {} as any, + version: '1.2.3', }, }, }, diff --git a/web/app/components/plugins/card/card-mock.ts b/web/app/components/plugins/card/card-mock.ts index 2ecd59a12b..4217c4d33a 100644 --- a/web/app/components/plugins/card/card-mock.ts +++ b/web/app/components/plugins/card/card-mock.ts @@ -1,6 +1,64 @@ import type { PluginDeclaration } from '../types' import { PluginType } from '../types' +export const toolNeko: PluginDeclaration = { + version: '0.0.1', + author: 'langgenius', + name: 'neko', + description: { + en_US: 'Neko is a cute cat.', + zh_Hans: '这是一只可爱的小猫。', + pt_BR: 'Neko is a cute cat.', + ja_JP: 'Neko is a cute cat.', + }, + icon: '241e5209ecc8b5ce6b7a29a8e50388e9c75b89c3047c6ecd8e552f26de758883.svg', + label: { + en_US: 'Neko', + zh_Hans: 'Neko', + pt_BR: 'Neko', + ja_JP: 'Neko', + }, + category: 'extension' as any, + created_at: '2024-07-12T08:03:44.658609Z', + resource: { + memory: 1048576, + permission: { + tool: { + enabled: true, + }, + model: { + enabled: true, + llm: true, + text_embedding: false, + rerank: false, + tts: false, + speech2text: false, + moderation: false, + }, + node: null, + endpoint: { + enabled: true, + }, + storage: { + enabled: true, + size: 1048576, + }, + }, + }, + plugins: { + tools: null, + models: null, + endpoints: [ + 'provider/neko.yaml', + ], + }, + tags: [], + verified: false, + tool: null, + model: null, + endpoint: null, +} + export const toolNotion = { type: PluginType.tool, org: 'Notion', diff --git a/web/app/components/plugins/install-plugin/install-from-local-package/index.tsx b/web/app/components/plugins/install-plugin/install-from-local-package/index.tsx index 5ecf9d27f3..d53be9e49b 100644 --- a/web/app/components/plugins/install-plugin/install-from-local-package/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-local-package/index.tsx @@ -40,7 +40,7 @@ const InstallFromLocalPackage: React.FC = ({ return t(`${i18nPrefix}.installFailed`) return t(`${i18nPrefix}.installPlugin`) - }, [step]) + }, [step, t]) const { getIconUrl } = useGetIcon() @@ -59,7 +59,7 @@ const InstallFromLocalPackage: React.FC = ({ icon, }) setStep(InstallStep.readyToInstall) - }, []) + }, [getIconUrl]) const handleUploadFail = useCallback((errorMsg: string) => { setErrorMsg(errorMsg) diff --git a/web/app/components/plugins/install-plugin/utils.ts b/web/app/components/plugins/install-plugin/utils.ts index 7677bf5b73..9f9508490e 100644 --- a/web/app/components/plugins/install-plugin/utils.ts +++ b/web/app/components/plugins/install-plugin/utils.ts @@ -3,6 +3,7 @@ import type { GitHubUrlInfo } from '@/app/components/plugins/types' export const pluginManifestToCardPluginProps = (pluginManifest: PluginDeclaration): Plugin => { return { + plugin_id: pluginManifest.plugin_unique_identifier, type: pluginManifest.category, category: pluginManifest.category, name: pluginManifest.name, @@ -25,6 +26,7 @@ export const pluginManifestToCardPluginProps = (pluginManifest: PluginDeclaratio export const pluginManifestInMarketToPluginProps = (pluginManifest: PluginManifestInMarket): Plugin => { return { + plugin_id: pluginManifest.plugin_unique_identifier, type: pluginManifest.category, category: pluginManifest.category, name: pluginManifest.name, diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index 386ecf5f7e..051f93906e 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -54,6 +54,7 @@ export type EndpointListItem = { // Plugin manifest export type PluginDeclaration = { + plugin_unique_identifier: string version: string author: string icon: string @@ -71,6 +72,7 @@ export type PluginDeclaration = { } export type PluginManifestInMarket = { + plugin_unique_identifier: string name: string org: string icon: string @@ -137,10 +139,11 @@ export type Permissions = { export type UpdateFromMarketPlacePayload = { originalPackageInfo: { id: string + payload: PluginDeclaration }, targetPackageInfo: { id: string - payload: PluginDeclaration + version: string } } diff --git a/web/app/components/plugins/update-plugin/from-market-place.tsx b/web/app/components/plugins/update-plugin/from-market-place.tsx index a4e215ccee..e0f9e2bc01 100644 --- a/web/app/components/plugins/update-plugin/from-market-place.tsx +++ b/web/app/components/plugins/update-plugin/from-market-place.tsx @@ -1,14 +1,15 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useMemo, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { RiInformation2Line } from '@remixicon/react' import { useTranslation } from 'react-i18next' import Card from '@/app/components/plugins/card' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' import Badge, { BadgeState } from '@/app/components/base/badge/index' -import { toolNotion } from '@/app/components/plugins/card/card-mock' import type { UpdateFromMarketPlacePayload } from '../types' +import { pluginManifestToCardPluginProps } from '@/app/components/plugins/install-plugin/utils' +import useGetIcon from '../install-plugin/base/use-get-icon' const i18nPrefix = 'plugin.upgrade' @@ -25,10 +26,23 @@ enum UploadStep { } const UpdatePluginModal: FC = ({ + payload, onSave, onCancel, }) => { + const { + originalPackageInfo, + targetPackageInfo, + } = payload const { t } = useTranslation() + const { getIconUrl } = useGetIcon() + const [icon, setIcon] = useState(originalPackageInfo.payload.icon) + useEffect(() => { + (async () => { + const icon = await getIconUrl(originalPackageInfo.payload.icon) + setIcon(icon) + })() + }, [originalPackageInfo, getIconUrl]) const [uploadStep, setUploadStep] = useState(UploadStep.notStarted) const configBtnText = useMemo(() => { return ({ @@ -65,12 +79,15 @@ const UpdatePluginModal: FC = ({
- {'1.2.0 -> 1.3.2'} + {`${originalPackageInfo.payload.version} -> ${targetPackageInfo.version}`}
{t(`${i18nPrefix}.usedInApps`, { num: 3 })}
From 0b90625e57c156da54a3407d569754b17cdd927a Mon Sep 17 00:00:00 2001 From: twwu Date: Tue, 5 Nov 2024 16:25:20 +0800 Subject: [PATCH 4/4] feat: integrate GitHub API for plugin version check and add access token support --- web/.env.example | 3 + .../plugins/install-plugin/hooks.ts | 23 +++-- .../install-from-github/index.tsx | 13 ++- .../components/plugins/plugin-item/action.tsx | 43 +++++++-- .../components/plugins/plugin-item/index.tsx | 24 ++--- web/app/layout.tsx | 1 + web/config/index.ts | 2 + web/package.json | 5 +- web/pnpm-lock.yaml | 95 ++++++++++++++++++- web/utils/semver.ts | 9 ++ 10 files changed, 181 insertions(+), 37 deletions(-) create mode 100644 web/utils/semver.ts diff --git a/web/.env.example b/web/.env.example index e0f6839b95..b17d637de7 100644 --- a/web/.env.example +++ b/web/.env.example @@ -29,3 +29,6 @@ NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS=60000 # CSP https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP NEXT_PUBLIC_CSP_WHITELIST= + +# Github Access Token, used for invoking Github API +NEXT_PUBLIC_GITHUB_ACCESS_TOKEN= diff --git a/web/app/components/plugins/install-plugin/hooks.ts b/web/app/components/plugins/install-plugin/hooks.ts index 2d4271e887..30db71e0f6 100644 --- a/web/app/components/plugins/install-plugin/hooks.ts +++ b/web/app/components/plugins/install-plugin/hooks.ts @@ -1,15 +1,25 @@ import { useState } from 'react' import Toast from '@/app/components/base/toast' import { uploadGitHub } from '@/service/plugins' +import { Octokit } from '@octokit/core' +import { GITHUB_ACCESS_TOKEN } from '@/config' export const useGitHubReleases = () => { - const fetchReleases = async (owner: string, repo: string, setReleases: (releases: any) => void) => { + const fetchReleases = async (owner: string, repo: string) => { try { - const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases`) - if (!res.ok) throw new Error('Failed to fetch releases') - const data = await res.json() + const octokit = new Octokit({ + auth: GITHUB_ACCESS_TOKEN, + }) + 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 = data.map((release: any) => ({ + const formattedReleases = res.data.map((release: any) => ({ tag_name: release.tag_name, assets: release.assets.map((asset: any) => ({ browser_download_url: asset.browser_download_url, @@ -17,13 +27,14 @@ export const useGitHubReleases = () => { })), })) - setReleases(formattedReleases) + return formattedReleases } catch (error) { Toast.notify({ type: 'error', message: 'Failed to fetch repository releases', }) + return [] } } 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 890424bfed..c60dd73765 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 @@ -64,13 +64,12 @@ const InstallFromGitHub: React.FC = ({ onClose }) => { }) break } - await fetchReleases(owner, repo, (fetchedReleases) => { - setState(prevState => ({ - ...prevState, - releases: fetchedReleases, - step: InstallStepFromGitHub.selectPackage, - })) - }) + const fetchedReleases = await fetchReleases(owner, repo) + setState(prevState => ({ + ...prevState, + releases: fetchedReleases, + step: InstallStepFromGitHub.selectPackage, + })) break } case InstallStepFromGitHub.selectPackage: { diff --git a/web/app/components/plugins/plugin-item/action.tsx b/web/app/components/plugins/plugin-item/action.tsx index 96b076511c..6d02eee345 100644 --- a/web/app/components/plugins/plugin-item/action.tsx +++ b/web/app/components/plugins/plugin-item/action.tsx @@ -10,13 +10,17 @@ import ActionButton from '../../base/action-button' import Tooltip from '../../base/tooltip' import Confirm from '../../base/confirm' import { uninstallPlugin } from '@/service/plugins' -import { usePluginPageContext } from '../plugin-page/context' +import { useGitHubReleases } from '../install-plugin/hooks' +import { compareVersion, getLatestVersion } from '@/utils/semver' +import Toast from '@/app/components/base/toast' const i18nPrefix = 'plugin.action' type Props = { - pluginId: string + author: string + installationId: string pluginName: string + version: string usedInApps: number isShowFetchNewVersion: boolean isShowInfo: boolean @@ -25,8 +29,10 @@ type Props = { meta?: MetaData } const Action: FC = ({ - pluginId, + author, + installationId, pluginName, + version, isShowFetchNewVersion, isShowInfo, isShowDelete, @@ -38,13 +44,35 @@ const Action: FC = ({ setTrue: showPluginInfo, setFalse: hidePluginInfo, }] = useBoolean(false) - const mutateInstalledPluginList = usePluginPageContext(v => v.mutateInstalledPluginList) const [deleting, { setTrue: showDeleting, setFalse: hideDeleting, }] = useBoolean(false) + const { fetchReleases } = useGitHubReleases() - const handleFetchNewVersion = () => { } + const handleFetchNewVersion = async () => { + try { + const fetchedReleases = await fetchReleases(author, pluginName) + const versions = fetchedReleases.map(release => release.tag_name) + const latestVersion = getLatestVersion(versions) + if (compareVersion(latestVersion, version) === 1) { + // todo: open plugin updating modal + console.log('New version available:', latestVersion) + } + else { + Toast.notify({ + type: 'info', + message: 'No new version available', + }) + } + } + catch { + Toast.notify({ + type: 'error', + message: 'Failed to compare versions', + }) + } + } const [isShowDeleteConfirm, { setTrue: showDeleteConfirm, @@ -53,14 +81,13 @@ const Action: FC = ({ const handleDelete = useCallback(async () => { showDeleting() - const res = await uninstallPlugin(pluginId) + const res = await uninstallPlugin(installationId) hideDeleting() if (res.success) { hideDeleteConfirm() - mutateInstalledPluginList() onDelete() } - }, [pluginId, onDelete]) + }, [installationId]) return (
{/* Only plugin installed from GitHub need to check if it's the new version */} diff --git a/web/app/components/plugins/plugin-item/index.tsx b/web/app/components/plugins/plugin-item/index.tsx index 5a40056a05..190f962167 100644 --- a/web/app/components/plugins/plugin-item/index.tsx +++ b/web/app/components/plugins/plugin-item/index.tsx @@ -37,6 +37,7 @@ const PluginItem: FC = ({ const { t } = useTranslation() const currentPluginDetail = usePluginPageContext(v => v.currentPluginDetail) const setCurrentPluginDetail = usePluginPageContext(v => v.setCurrentPluginDetail) + const mutateInstalledPluginList = usePluginPageContext(v => v.mutateInstalledPluginList) const { source, @@ -44,16 +45,10 @@ const PluginItem: FC = ({ installation_id, endpoints_active, meta, - version, - latest_version, plugin_id, + version, } = plugin const { category, author, name, label, description, icon, verified } = plugin.declaration - // Only plugin installed from GitHub need to check if it's the new version - // todo check version manually - const hasNewVersion = useMemo(() => { - return source === PluginSource.github && latest_version !== version - }, [source, latest_version, version]) const orgName = useMemo(() => { return [PluginSource.github, PluginSource.marketplace].includes(source) ? author : '' @@ -79,6 +74,7 @@ const PluginItem: FC = ({
{`plugin-${installation_id}-logo`} @@ -87,20 +83,24 @@ const PluginItem: FC = ({
{verified && <RiVerifiedBadgeLine className="shrink-0 ml-0.5 w-4 h-4 text-text-accent" />} - <Badge className='ml-1' text={plugin.version} hasRedCornerMark={hasNewVersion} /> + <Badge className='ml-1' text={plugin.version} /> </div> <div className='flex items-center justify-between'> <Description text={description[tLocale]} descriptionLineRows={1}></Description> <div onClick={e => e.stopPropagation()}> <Action - pluginId={installation_id} - pluginName={label[tLocale]} + installationId={installation_id} + author={author} + pluginName={name} + version={version} usedInApps={5} - isShowFetchNewVersion={hasNewVersion} + isShowFetchNewVersion={source === PluginSource.github} isShowInfo={source === PluginSource.github} isShowDelete meta={meta} - onDelete={() => {}} + onDelete={() => { + mutateInstalledPluginList() + }} /> </div> </div> diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 0fc56c4509..8fa7f92851 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -45,6 +45,7 @@ const LocaleLayout = ({ data-public-maintenance-notice={process.env.NEXT_PUBLIC_MAINTENANCE_NOTICE} data-public-site-about={process.env.NEXT_PUBLIC_SITE_ABOUT} data-public-text-generation-timeout-ms={process.env.NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS} + data-public-github-access-token={process.env.NEXT_PUBLIC_GITHUB_ACCESS_TOKEN} > <BrowserInitor> <SentryInitor> diff --git a/web/config/index.ts b/web/config/index.ts index 85cb850393..1de973f9a2 100644 --- a/web/config/index.ts +++ b/web/config/index.ts @@ -271,3 +271,5 @@ else if (globalThis.document?.body?.getAttribute('data-public-text-generation-ti export const TEXT_GENERATION_TIMEOUT_MS = textGenerationTimeoutMs export const DISABLE_UPLOAD_IMAGE_AS_ICON = process.env.NEXT_PUBLIC_DISABLE_UPLOAD_IMAGE_AS_ICON === 'true' + +export const GITHUB_ACCESS_TOKEN = process.env.NEXT_PUBLIC_GITHUB_ACCESS_TOKEN || globalThis.document?.body?.getAttribute('data-public-github-access-token') || '' diff --git a/web/package.json b/web/package.json index a1ebb26eea..42ad410926 100644 --- a/web/package.json +++ b/web/package.json @@ -37,6 +37,7 @@ "@mdx-js/react": "^2.3.0", "@monaco-editor/react": "^4.6.0", "@next/mdx": "^14.0.4", + "@octokit/core": "^6.1.2", "@remixicon/react": "^4.3.0", "@sentry/react": "^7.54.0", "@sentry/utils": "^7.54.0", @@ -98,6 +99,7 @@ "remark-gfm": "^3.0.1", "remark-math": "^5.1.1", "scheduler": "^0.23.0", + "semver": "^7.6.3", "server-only": "^0.0.1", "sharp": "^0.33.5", "sortablejs": "^1.15.3", @@ -144,6 +146,7 @@ "@types/react-window": "^1.8.8", "@types/react-window-infinite-loader": "^1.0.9", "@types/recordrtc": "^5.6.14", + "@types/semver": "^7.5.8", "@types/sortablejs": "^1.15.1", "@types/uuid": "^10.0.0", "autoprefixer": "^10.4.14", @@ -153,9 +156,9 @@ "eslint": "^9.13.0", "eslint-config-next": "^15.0.0", "eslint-plugin-react-hooks": "^5.0.0", - "husky": "^9.1.6", "eslint-plugin-react-refresh": "^0.4.13", "eslint-plugin-storybook": "^0.10.1", + "husky": "^9.1.6", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "lint-staged": "^15.2.10", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index b76da7ca7c..a919c971b7 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -52,6 +52,9 @@ importers: '@next/mdx': specifier: ^14.0.4 version: 14.2.15(@mdx-js/loader@2.3.0(webpack@5.95.0(esbuild@0.23.1)(uglify-js@3.19.3)))(@mdx-js/react@2.3.0(react@18.2.0)) + '@octokit/core': + specifier: ^6.1.2 + version: 6.1.2 '@remixicon/react': specifier: ^4.3.0 version: 4.3.0(react@18.2.0) @@ -235,6 +238,9 @@ importers: scheduler: specifier: ^0.23.0 version: 0.23.2 + semver: + specifier: ^7.6.3 + version: 7.6.3 server-only: specifier: ^0.0.1 version: 0.0.1 @@ -368,6 +374,9 @@ importers: '@types/recordrtc': specifier: ^5.6.14 version: 5.6.14 + '@types/semver': + specifier: ^7.5.8 + version: 7.5.8 '@types/sortablejs': specifier: ^1.15.1 version: 1.15.8 @@ -1871,6 +1880,36 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@octokit/auth-token@5.1.1': + resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} + engines: {node: '>= 18'} + + '@octokit/core@6.1.2': + resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==} + engines: {node: '>= 18'} + + '@octokit/endpoint@10.1.1': + resolution: {integrity: sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==} + engines: {node: '>= 18'} + + '@octokit/graphql@8.1.1': + resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==} + engines: {node: '>= 18'} + + '@octokit/openapi-types@22.2.0': + resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} + + '@octokit/request-error@6.1.5': + resolution: {integrity: sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==} + engines: {node: '>= 18'} + + '@octokit/request@9.1.3': + resolution: {integrity: sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==} + engines: {node: '>= 18'} + + '@octokit/types@13.6.1': + resolution: {integrity: sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==} + '@parcel/watcher-android-arm64@2.4.1': resolution: {integrity: sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==} engines: {node: '>= 10.0.0'} @@ -3158,6 +3197,9 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + before-after-hook@3.0.2: + resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} + better-opn@3.0.2: resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} engines: {node: '>=12.0.0'} @@ -7806,6 +7848,9 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + universal-user-agent@7.0.2: + resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} + universalify@0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} @@ -9925,6 +9970,46 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@octokit/auth-token@5.1.1': {} + + '@octokit/core@6.1.2': + dependencies: + '@octokit/auth-token': 5.1.1 + '@octokit/graphql': 8.1.1 + '@octokit/request': 9.1.3 + '@octokit/request-error': 6.1.5 + '@octokit/types': 13.6.1 + before-after-hook: 3.0.2 + universal-user-agent: 7.0.2 + + '@octokit/endpoint@10.1.1': + dependencies: + '@octokit/types': 13.6.1 + universal-user-agent: 7.0.2 + + '@octokit/graphql@8.1.1': + dependencies: + '@octokit/request': 9.1.3 + '@octokit/types': 13.6.1 + universal-user-agent: 7.0.2 + + '@octokit/openapi-types@22.2.0': {} + + '@octokit/request-error@6.1.5': + dependencies: + '@octokit/types': 13.6.1 + + '@octokit/request@9.1.3': + dependencies: + '@octokit/endpoint': 10.1.1 + '@octokit/request-error': 6.1.5 + '@octokit/types': 13.6.1 + universal-user-agent: 7.0.2 + + '@octokit/types@13.6.1': + dependencies: + '@octokit/openapi-types': 22.2.0 + '@parcel/watcher-android-arm64@2.4.1': optional: true @@ -11618,6 +11703,8 @@ snapshots: base64-js@1.5.1: {} + before-after-hook@3.0.2: {} + better-opn@3.0.2: dependencies: open: 8.4.2 @@ -12757,7 +12844,7 @@ snapshots: debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 9.13.0(jiti@1.21.6) - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import-x@4.3.1(eslint@9.13.0(jiti@1.21.6))(typescript@4.9.5))(eslint-plugin-import@2.31.0)(eslint@9.13.0(jiti@1.21.6)))(eslint@9.13.0(jiti@1.21.6)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.13.0(jiti@1.21.6)) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -12775,7 +12862,7 @@ snapshots: dependencies: eslint: 9.13.0(jiti@1.21.6) - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import-x@4.3.1(eslint@9.13.0(jiti@1.21.6))(typescript@4.9.5))(eslint-plugin-import@2.31.0)(eslint@9.13.0(jiti@1.21.6)))(eslint@9.13.0(jiti@1.21.6)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.13.0(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: @@ -12831,7 +12918,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.13.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import-x@4.3.1(eslint@9.13.0(jiti@1.21.6))(typescript@4.9.5))(eslint-plugin-import@2.31.0)(eslint@9.13.0(jiti@1.21.6)))(eslint@9.13.0(jiti@1.21.6)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.13.0(jiti@1.21.6)) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -17577,6 +17664,8 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 + universal-user-agent@7.0.2: {} + universalify@0.2.0: {} universalify@2.0.1: {} diff --git a/web/utils/semver.ts b/web/utils/semver.ts new file mode 100644 index 0000000000..f1b9eb8d7e --- /dev/null +++ b/web/utils/semver.ts @@ -0,0 +1,9 @@ +import semver from 'semver' + +export const getLatestVersion = (versionList: string[]) => { + return semver.rsort(versionList)[0] +} + +export const compareVersion = (v1: string, v2: string) => { + return semver.compare(v1, v2) +}