mirror of
https://github.com/pompurin404/mihomo-party.git
synced 2024-11-16 03:32:17 +08:00
support auto update (#59)
This commit is contained in:
parent
e122e21693
commit
5fc26d2249
25
.github/workflows/build.yml
vendored
25
.github/workflows/build.yml
vendored
|
@ -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
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
### New Features
|
||||
|
||||
- 支持Yaml格式覆写配置
|
||||
- Yaml格式覆写配置支持添加规则/代理
|
||||
- 支持应用内自动更新
|
||||
|
|
658
pnpm-lock.yaml
658
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
12
scripts/updater.mjs
Normal file
12
scripts/updater.mjs
Normal 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))
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
79
src/renderer/src/components/updater/updater-modal.tsx
Normal file
79
src/renderer/src/components/updater/updater-modal.tsx
Normal 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
|
|
@ -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'))
|
||||
}
|
||||
|
|
5
src/shared/types.d.ts
vendored
5
src/shared/types.d.ts
vendored
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user