support auto update (#59)

This commit is contained in:
MystiPanda 2024-08-18 15:20:45 +08:00 committed by GitHub
parent e122e21693
commit 5fc26d2249
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 866 additions and 25 deletions

View File

@ -60,7 +60,6 @@ jobs:
files: |
dist/*setup.exe
dist/*portable.7z
dist/latest.yml
body_path: changelog.md
token: ${{ secrets.GITHUB_TOKEN }}
@ -166,6 +165,30 @@ jobs:
body_path: changelog.md
token: ${{ secrets.GITHUB_TOKEN }}
updater:
if: startsWith(github.ref, 'refs/tags/v')
needs: [windows, macos]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Nodejs
uses: actions/setup-node@v4
with:
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Build Latest
run: pnpm install && pnpm updater
- name: Publish Release
uses: softprops/action-gh-release@v2
with:
files: latest.yml
body_path: changelog.md
token: ${{ secrets.GITHUB_TOKEN }}
aur-release-updater:
strategy:
fail-fast: false

View File

@ -1,4 +1,3 @@
### New Features
- 支持Yaml格式覆写配置
- Yaml格式覆写配置支持添加规则/代理
- 支持应用内自动更新

File diff suppressed because it is too large Load Diff

12
scripts/updater.mjs Normal file
View File

@ -0,0 +1,12 @@
import yaml from 'yaml'
import { readFileSync, writeFileSync } from 'fs'
const pkg = readFileSync('package.json', 'utf-8')
const changelog = readFileSync('changelog.md', 'utf-8')
const { version } = JSON.parse(pkg)
const latest = {
version,
changelog
}
writeFileSync('latest.yml', yaml.stringify(latest))

View File

@ -1,9 +1,13 @@
import axios from 'axios'
import yaml from 'yaml'
import { app } from 'electron'
import { app, shell } from 'electron'
import { getControledMihomoConfig } from '../config'
import { dataDir, isPortable } from '../utils/dirs'
import { rm, writeFile } from 'fs/promises'
import path from 'path'
import { existsSync } from 'fs'
export async function checkUpdate(): Promise<string | undefined> {
export async function checkUpdate(): Promise<IAppVersion | undefined> {
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
const res = await axios.get(
'https://github.com/pompurin404/mihomo-party/releases/latest/download/latest.yml',
@ -16,12 +20,52 @@ export async function checkUpdate(): Promise<string | undefined> {
}
}
)
const latest = yaml.parse(res.data) as { version: string }
const remoteVersion = latest.version
const latest = yaml.parse(res.data) as IAppVersion
const currentVersion = app.getVersion()
if (remoteVersion !== currentVersion) {
return remoteVersion
if (latest.version !== currentVersion) {
return latest
} else {
return undefined
}
}
export async function downloadAndInstallUpdate(version: string): Promise<void> {
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
const baseUrl = `https://github.com/pompurin404/mihomo-party/releases/download/v${version}/`
const fileMap = {
'win32-x64': `mihomo-party-windows-${version}-x64-setup.exe`,
'win32-ia32': `mihomo-party-windows-${version}-ia32-setup.exe`,
'win32-arm64': `mihomo-party-windows-${version}-arm64-setup.exe`,
'darwin-x64': `mihomo-party-macos-${version}-x64.dmg`,
'darwin-arm64': `mihomo-party-macos-${version}-arm64.dmg`
}
const file = fileMap[`${process.platform}-${process.arch}`]
if (isPortable()) {
throw new Error('便携模式不支持自动更新')
}
if (!file) {
throw new Error('不支持自动更新,请手动下载更新')
}
try {
if (!existsSync(path.join(dataDir(), file))) {
const res = await axios.get(`${baseUrl}${file}`, {
responseType: 'arraybuffer',
proxy: {
protocol: 'http',
host: '127.0.0.1',
port: mixedPort
},
headers: {
'Content-Type': 'application/octet-stream'
}
})
await writeFile(path.join(dataDir(), file), res.data)
}
await shell.openPath(path.join(dataDir(), file))
app.quit()
} catch (e) {
rm(path.join(dataDir(), file))
throw e
}
}

View File

@ -46,7 +46,7 @@ import {
} from '../config'
import { isEncryptionAvailable, manualGrantCorePermition, restartCore } from '../core/manager'
import { triggerSysProxy } from '../sys/sysproxy'
import { checkUpdate } from '../resolve/autoUpdater'
import { checkUpdate, downloadAndInstallUpdate } from '../resolve/autoUpdater'
import { getFilePath, openUWPTool, readTextFile, setupFirewall } from '../sys/misc'
import { getRuntimeConfig, getRuntimeConfigStr } from '../core/factory'
import { isPortable, setPortable } from './dirs'
@ -131,6 +131,9 @@ export function registerIpcMainHandlers(): void {
ipcMain.handle('readTextFile', (_e, filePath) => ipcErrorWrapper(readTextFile)(filePath))
ipcMain.handle('getRuntimeConfigStr', ipcErrorWrapper(getRuntimeConfigStr))
ipcMain.handle('getRuntimeConfig', ipcErrorWrapper(getRuntimeConfig))
ipcMain.handle('downloadAndInstallUpdate', (_e, version) =>
ipcErrorWrapper(downloadAndInstallUpdate)(version)
)
ipcMain.handle('checkUpdate', ipcErrorWrapper(checkUpdate))
ipcMain.handle('getVersion', () => app.getVersion())
ipcMain.handle('platform', () => process.platform)

View File

@ -1,32 +1,44 @@
import { Button } from '@nextui-org/react'
import { useAppConfig } from '@renderer/hooks/use-app-config'
import { checkUpdate } from '@renderer/utils/ipc'
import React from 'react'
import React, { useState } from 'react'
import useSWR from 'swr'
import UpdaterModal from './updater-modal'
const UpdaterButton: React.FC = () => {
const { appConfig } = useAppConfig()
const { autoCheckUpdate } = appConfig || {}
const { data: version } = useSWR(
const [openModal, setOpenModal] = useState(false)
const { data: latest } = useSWR(
autoCheckUpdate ? 'checkUpdate' : undefined,
autoCheckUpdate ? checkUpdate : (): void => {},
autoCheckUpdate ? checkUpdate : (): undefined => {},
{
refreshInterval: 1000 * 60 * 10
}
)
if (!version) return null
if (!latest) return null
return (
<Button
color="danger"
size="sm"
onPress={() => {
open(`https://github.com/pompurin404/mihomo-party/releases/tag/v${version}`)
}}
>
v{version}
</Button>
<>
{openModal && (
<UpdaterModal
version={latest.version}
changelog={latest.changelog}
onClose={() => {
setOpenModal(false)
}}
/>
)}
<Button
color="danger"
size="sm"
onPress={() => {
setOpenModal(true)
}}
>
v{latest.version}
</Button>
</>
)
}

View File

@ -0,0 +1,79 @@
import {
Modal,
ModalContent,
ModalHeader,
ModalBody,
ModalFooter,
Button,
Code
} from '@nextui-org/react'
import ReactMarkdown from 'react-markdown'
import React, { useState } from 'react'
import { downloadAndInstallUpdate } from '@renderer/utils/ipc'
interface Props {
version: string
changelog: string
onClose: () => void
}
const UpdaterModal: React.FC<Props> = (props) => {
const { version, changelog, onClose } = props
const [downloading, setDownloading] = useState(false)
const onUpdate = async (): Promise<void> => {
try {
await downloadAndInstallUpdate(version)
} catch (e) {
alert(e)
}
}
return (
<Modal
backdrop="blur"
hideCloseButton
isOpen={true}
onOpenChange={onClose}
scrollBehavior="inside"
>
<ModalContent className="h-full w-[calc(100%-100px)]">
<ModalHeader className="flex">v{version} </ModalHeader>
<ModalBody className="h-full">
<ReactMarkdown
className="markdown-body select-text"
components={{
code: ({ children }) => <Code size="sm">{children}</Code>,
h3: ({ ...props }) => <h3 className="text-lg font-bold" {...props} />,
li: ({ children }) => <li className="list-disc list-inside">{children}</li>
}}
>
{changelog}
</ReactMarkdown>
</ModalBody>
<ModalFooter>
<Button variant="light" onPress={onClose}>
</Button>
<Button
color="primary"
isLoading={downloading}
onPress={async () => {
try {
setDownloading(true)
await onUpdate()
onClose()
} catch (e) {
alert(e)
} finally {
setDownloading(false)
}
}}
>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
)
}
export default UpdaterModal

View File

@ -225,10 +225,16 @@ export async function getRuntimeConfig(): Promise<IMihomoConfig> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getRuntimeConfig'))
}
export async function checkUpdate(): Promise<string | undefined> {
export async function checkUpdate(): Promise<IAppVersion | undefined> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('checkUpdate'))
}
export async function downloadAndInstallUpdate(version: string): Promise<void> {
return ipcErrorWrapper(
await window.electron.ipcRenderer.invoke('downloadAndInstallUpdate', version)
)
}
export async function getVersion(): Promise<string> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getVersion'))
}

View File

@ -37,6 +37,11 @@ type TunStack = 'gvisor' | 'mixed' | 'system'
type FindProcessMode = 'off' | 'strict' | 'always'
type DnsMode = 'normal' | 'fake-ip' | 'redir-host'
interface IAppVersion {
version: string
changelog: string
}
interface IMihomoVersion {
version: string
meta: boolean