mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 05:00:30 +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: |
|
files: |
|
||||||
dist/*setup.exe
|
dist/*setup.exe
|
||||||
dist/*portable.7z
|
dist/*portable.7z
|
||||||
dist/latest.yml
|
|
||||||
body_path: changelog.md
|
body_path: changelog.md
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@ -166,6 +165,30 @@ jobs:
|
|||||||
body_path: changelog.md
|
body_path: changelog.md
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
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:
|
aur-release-updater:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
### New Features
|
### New Features
|
||||||
|
|
||||||
- 支持Yaml格式覆写配置
|
- 支持应用内自动更新
|
||||||
- Yaml格式覆写配置支持添加规则/代理
|
|
||||||
|
|||||||
658
pnpm-lock.yaml
generated
658
pnpm-lock.yaml
generated
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 axios from 'axios'
|
||||||
import yaml from 'yaml'
|
import yaml from 'yaml'
|
||||||
import { app } from 'electron'
|
import { app, shell } from 'electron'
|
||||||
import { getControledMihomoConfig } from '../config'
|
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 { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
|
||||||
const res = await axios.get(
|
const res = await axios.get(
|
||||||
'https://github.com/pompurin404/mihomo-party/releases/latest/download/latest.yml',
|
'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 latest = yaml.parse(res.data) as IAppVersion
|
||||||
const remoteVersion = latest.version
|
|
||||||
const currentVersion = app.getVersion()
|
const currentVersion = app.getVersion()
|
||||||
if (remoteVersion !== currentVersion) {
|
if (latest.version !== currentVersion) {
|
||||||
return remoteVersion
|
return latest
|
||||||
} else {
|
} else {
|
||||||
return undefined
|
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'
|
} from '../config'
|
||||||
import { isEncryptionAvailable, manualGrantCorePermition, restartCore } from '../core/manager'
|
import { isEncryptionAvailable, manualGrantCorePermition, restartCore } from '../core/manager'
|
||||||
import { triggerSysProxy } from '../sys/sysproxy'
|
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 { getFilePath, openUWPTool, readTextFile, setupFirewall } from '../sys/misc'
|
||||||
import { getRuntimeConfig, getRuntimeConfigStr } from '../core/factory'
|
import { getRuntimeConfig, getRuntimeConfigStr } from '../core/factory'
|
||||||
import { isPortable, setPortable } from './dirs'
|
import { isPortable, setPortable } from './dirs'
|
||||||
@ -131,6 +131,9 @@ export function registerIpcMainHandlers(): void {
|
|||||||
ipcMain.handle('readTextFile', (_e, filePath) => ipcErrorWrapper(readTextFile)(filePath))
|
ipcMain.handle('readTextFile', (_e, filePath) => ipcErrorWrapper(readTextFile)(filePath))
|
||||||
ipcMain.handle('getRuntimeConfigStr', ipcErrorWrapper(getRuntimeConfigStr))
|
ipcMain.handle('getRuntimeConfigStr', ipcErrorWrapper(getRuntimeConfigStr))
|
||||||
ipcMain.handle('getRuntimeConfig', ipcErrorWrapper(getRuntimeConfig))
|
ipcMain.handle('getRuntimeConfig', ipcErrorWrapper(getRuntimeConfig))
|
||||||
|
ipcMain.handle('downloadAndInstallUpdate', (_e, version) =>
|
||||||
|
ipcErrorWrapper(downloadAndInstallUpdate)(version)
|
||||||
|
)
|
||||||
ipcMain.handle('checkUpdate', ipcErrorWrapper(checkUpdate))
|
ipcMain.handle('checkUpdate', ipcErrorWrapper(checkUpdate))
|
||||||
ipcMain.handle('getVersion', () => app.getVersion())
|
ipcMain.handle('getVersion', () => app.getVersion())
|
||||||
ipcMain.handle('platform', () => process.platform)
|
ipcMain.handle('platform', () => process.platform)
|
||||||
|
|||||||
@ -1,32 +1,44 @@
|
|||||||
import { Button } from '@nextui-org/react'
|
import { Button } from '@nextui-org/react'
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
import { checkUpdate } from '@renderer/utils/ipc'
|
import { checkUpdate } from '@renderer/utils/ipc'
|
||||||
import React from 'react'
|
import React, { useState } from 'react'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
|
import UpdaterModal from './updater-modal'
|
||||||
|
|
||||||
const UpdaterButton: React.FC = () => {
|
const UpdaterButton: React.FC = () => {
|
||||||
const { appConfig } = useAppConfig()
|
const { appConfig } = useAppConfig()
|
||||||
const { autoCheckUpdate } = appConfig || {}
|
const { autoCheckUpdate } = appConfig || {}
|
||||||
|
const [openModal, setOpenModal] = useState(false)
|
||||||
const { data: version } = useSWR(
|
const { data: latest } = useSWR(
|
||||||
autoCheckUpdate ? 'checkUpdate' : undefined,
|
autoCheckUpdate ? 'checkUpdate' : undefined,
|
||||||
autoCheckUpdate ? checkUpdate : (): void => {},
|
autoCheckUpdate ? checkUpdate : (): undefined => {},
|
||||||
{
|
{
|
||||||
refreshInterval: 1000 * 60 * 10
|
refreshInterval: 1000 * 60 * 10
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (!version) return null
|
if (!latest) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<>
|
||||||
color="danger"
|
{openModal && (
|
||||||
size="sm"
|
<UpdaterModal
|
||||||
onPress={() => {
|
version={latest.version}
|
||||||
open(`https://github.com/pompurin404/mihomo-party/releases/tag/v${version}`)
|
changelog={latest.changelog}
|
||||||
}}
|
onClose={() => {
|
||||||
>
|
setOpenModal(false)
|
||||||
v{version}
|
}}
|
||||||
</Button>
|
/>
|
||||||
|
)}
|
||||||
|
<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'))
|
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'))
|
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> {
|
export async function getVersion(): Promise<string> {
|
||||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getVersion'))
|
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 FindProcessMode = 'off' | 'strict' | 'always'
|
||||||
type DnsMode = 'normal' | 'fake-ip' | 'redir-host'
|
type DnsMode = 'normal' | 'fake-ip' | 'redir-host'
|
||||||
|
|
||||||
|
interface IAppVersion {
|
||||||
|
version: string
|
||||||
|
changelog: string
|
||||||
|
}
|
||||||
|
|
||||||
interface IMihomoVersion {
|
interface IMihomoVersion {
|
||||||
version: string
|
version: string
|
||||||
meta: boolean
|
meta: boolean
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user