mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-04-13 05:20:28 +08:00
feat: add tauri-updater support for Autobuild channel
- Refactor updater.mjs: extract shared utilities, remove dead alpha channel, add `autobuild` channel via CLI arg - Add override-updater-endpoints.mjs to switch tauri.conf.json endpoints to updater-autobuild at build time - Add release-update-autobuild job to autobuild workflow - Change allowDowngrades to true so custom compareVersions handles build-metadata versions (+autobuild.MMDD.hash) correctly - Fix original bug: linux-x86/i686 wrote .url instead of .signature in the .sig handler
This commit is contained in:
parent
b8fbabae04
commit
3320b300de
41
.github/workflows/autobuild.yml
vendored
41
.github/workflows/autobuild.yml
vendored
@ -206,6 +206,9 @@ jobs:
|
|||||||
- name: Release ${{ env.TAG_CHANNEL }} Version
|
- name: Release ${{ env.TAG_CHANNEL }} Version
|
||||||
run: pnpm release-version autobuild-latest
|
run: pnpm release-version autobuild-latest
|
||||||
|
|
||||||
|
- name: Override updater endpoints for autobuild
|
||||||
|
run: pnpm override-updater-endpoints autobuild
|
||||||
|
|
||||||
- name: Add Rust Target
|
- name: Add Rust Target
|
||||||
run: |
|
run: |
|
||||||
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
|
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
|
||||||
@ -304,6 +307,9 @@ jobs:
|
|||||||
- name: Release ${{ env.TAG_CHANNEL }} Version
|
- name: Release ${{ env.TAG_CHANNEL }} Version
|
||||||
run: pnpm release-version autobuild-latest
|
run: pnpm release-version autobuild-latest
|
||||||
|
|
||||||
|
- name: Override updater endpoints for autobuild
|
||||||
|
run: pnpm override-updater-endpoints autobuild
|
||||||
|
|
||||||
- name: 'Setup for linux'
|
- name: 'Setup for linux'
|
||||||
run: |-
|
run: |-
|
||||||
sudo ls -lR /etc/apt/
|
sudo ls -lR /etc/apt/
|
||||||
@ -449,6 +455,9 @@ jobs:
|
|||||||
- name: Release ${{ env.TAG_CHANNEL }} Version
|
- name: Release ${{ env.TAG_CHANNEL }} Version
|
||||||
run: pnpm release-version autobuild-latest
|
run: pnpm release-version autobuild-latest
|
||||||
|
|
||||||
|
- name: Override updater endpoints for autobuild
|
||||||
|
run: pnpm override-updater-endpoints autobuild
|
||||||
|
|
||||||
- name: Download WebView2 Runtime
|
- name: Download WebView2 Runtime
|
||||||
run: |
|
run: |
|
||||||
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/133.0.3065.92/Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab
|
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/133.0.3065.92/Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab
|
||||||
@ -510,6 +519,37 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
release-update-autobuild:
|
||||||
|
name: Release Autobuild Updater
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
[
|
||||||
|
autobuild-x86-windows-macos-linux,
|
||||||
|
autobuild-arm-linux,
|
||||||
|
autobuild-x86-arm-windows_webview2,
|
||||||
|
]
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Install Node
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: '24.14.1'
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v5.0.0
|
||||||
|
name: Install pnpm
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Pnpm install
|
||||||
|
run: pnpm i
|
||||||
|
|
||||||
|
- name: Release autobuild updater file
|
||||||
|
run: pnpm updater-autobuild
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
notify-telegram:
|
notify-telegram:
|
||||||
name: Notify Telegram
|
name: Notify Telegram
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -519,6 +559,7 @@ jobs:
|
|||||||
autobuild-x86-windows-macos-linux,
|
autobuild-x86-windows-macos-linux,
|
||||||
autobuild-arm-linux,
|
autobuild-arm-linux,
|
||||||
autobuild-x86-arm-windows_webview2,
|
autobuild-x86-arm-windows_webview2,
|
||||||
|
release-update-autobuild,
|
||||||
]
|
]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
|
|||||||
@ -16,7 +16,9 @@
|
|||||||
"web:serve": "vite preview",
|
"web:serve": "vite preview",
|
||||||
"prebuild": "node scripts/prebuild.mjs",
|
"prebuild": "node scripts/prebuild.mjs",
|
||||||
"updater": "node scripts/updater.mjs",
|
"updater": "node scripts/updater.mjs",
|
||||||
|
"updater-autobuild": "node scripts/updater.mjs autobuild",
|
||||||
"updater-fixed-webview2": "node scripts/updater-fixed-webview2.mjs",
|
"updater-fixed-webview2": "node scripts/updater-fixed-webview2.mjs",
|
||||||
|
"override-updater-endpoints": "node scripts/override-updater-endpoints.mjs",
|
||||||
"portable": "node scripts/portable.mjs",
|
"portable": "node scripts/portable.mjs",
|
||||||
"portable-fixed-webview2": "node scripts/portable-fixed-webview2.mjs",
|
"portable-fixed-webview2": "node scripts/portable-fixed-webview2.mjs",
|
||||||
"fix-alpha-version": "node scripts/fix-alpha_version.mjs",
|
"fix-alpha-version": "node scripts/fix-alpha_version.mjs",
|
||||||
|
|||||||
32
scripts/override-updater-endpoints.mjs
Normal file
32
scripts/override-updater-endpoints.mjs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Override updater endpoints in tauri.conf.json for a specific channel.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node scripts/override-updater-endpoints.mjs <channel>
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* node scripts/override-updater-endpoints.mjs autobuild
|
||||||
|
* # Changes: .../releases/download/updater/update.json
|
||||||
|
* # → : .../releases/download/updater-autobuild/update.json
|
||||||
|
*/
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
const channel = process.argv[2]
|
||||||
|
if (!channel) {
|
||||||
|
console.error('Usage: node scripts/override-updater-endpoints.mjs <channel>')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const configPath = path.join(process.cwd(), 'src-tauri', 'tauri.conf.json')
|
||||||
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'))
|
||||||
|
|
||||||
|
config.plugins.updater.endpoints = config.plugins.updater.endpoints.map(
|
||||||
|
(endpoint) =>
|
||||||
|
endpoint.replace('/download/updater/', `/download/updater-${channel}/`),
|
||||||
|
)
|
||||||
|
|
||||||
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
|
||||||
|
|
||||||
|
console.log(`[INFO]: Updater endpoints switched to "${channel}" channel:`)
|
||||||
|
config.plugins.updater.endpoints.forEach((e) => console.log(` ${e}`))
|
||||||
@ -3,26 +3,203 @@ import fetch from 'node-fetch'
|
|||||||
|
|
||||||
import { resolveUpdateLog, resolveUpdateLogDefault } from './updatelog.mjs'
|
import { resolveUpdateLog, resolveUpdateLogDefault } from './updatelog.mjs'
|
||||||
|
|
||||||
// Add stable update JSON filenames
|
/// get the signature file content
|
||||||
const UPDATE_TAG_NAME = 'updater'
|
async function getSignature(url) {
|
||||||
const UPDATE_JSON_FILE = 'update.json'
|
const response = await fetch(url, {
|
||||||
const UPDATE_JSON_PROXY = 'update-proxy.json'
|
method: 'GET',
|
||||||
// Add alpha update JSON filenames
|
headers: { 'Content-Type': 'application/octet-stream' },
|
||||||
const ALPHA_TAG_NAME = 'updater-alpha'
|
})
|
||||||
const ALPHA_UPDATE_JSON_FILE = 'update.json'
|
return response.text()
|
||||||
const ALPHA_UPDATE_JSON_PROXY = 'update-proxy.json'
|
}
|
||||||
|
|
||||||
/// generate update.json
|
function buildPlatformData() {
|
||||||
/// upload to update tag's release asset
|
return {
|
||||||
async function resolveUpdater() {
|
win64: { signature: '', url: '' },
|
||||||
if (process.env.GITHUB_TOKEN === undefined) {
|
linux: { signature: '', url: '' },
|
||||||
throw new Error('GITHUB_TOKEN is required')
|
darwin: { signature: '', url: '' },
|
||||||
|
'darwin-aarch64': { signature: '', url: '' },
|
||||||
|
'darwin-intel': { signature: '', url: '' },
|
||||||
|
'darwin-x86_64': { signature: '', url: '' },
|
||||||
|
'linux-x86_64': { signature: '', url: '' },
|
||||||
|
'linux-x86': { signature: '', url: '' },
|
||||||
|
'linux-i686': { signature: '', url: '' },
|
||||||
|
'linux-aarch64': { signature: '', url: '' },
|
||||||
|
'linux-armv7': { signature: '', url: '' },
|
||||||
|
'windows-x86_64': { signature: '', url: '' },
|
||||||
|
'windows-aarch64': { signature: '', url: '' },
|
||||||
|
'windows-x86': { signature: '', url: '' },
|
||||||
|
'windows-i686': { signature: '', url: '' },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map release assets to platform update data
|
||||||
|
async function processAssets(release, updateData) {
|
||||||
|
const promises = release.assets.map(async (asset) => {
|
||||||
|
const { name, browser_download_url } = asset
|
||||||
|
|
||||||
|
// win64 url
|
||||||
|
if (name.endsWith('x64-setup.exe')) {
|
||||||
|
updateData.platforms.win64.url = browser_download_url
|
||||||
|
updateData.platforms['windows-x86_64'].url = browser_download_url
|
||||||
|
}
|
||||||
|
// win64 signature
|
||||||
|
if (name.endsWith('x64-setup.exe.sig')) {
|
||||||
|
const sig = await getSignature(browser_download_url)
|
||||||
|
updateData.platforms.win64.signature = sig
|
||||||
|
updateData.platforms['windows-x86_64'].signature = sig
|
||||||
|
}
|
||||||
|
|
||||||
|
// win32 url
|
||||||
|
if (name.endsWith('x86-setup.exe')) {
|
||||||
|
updateData.platforms['windows-x86'].url = browser_download_url
|
||||||
|
updateData.platforms['windows-i686'].url = browser_download_url
|
||||||
|
}
|
||||||
|
// win32 signature
|
||||||
|
if (name.endsWith('x86-setup.exe.sig')) {
|
||||||
|
const sig = await getSignature(browser_download_url)
|
||||||
|
updateData.platforms['windows-x86'].signature = sig
|
||||||
|
updateData.platforms['windows-i686'].signature = sig
|
||||||
|
}
|
||||||
|
|
||||||
|
// win arm url
|
||||||
|
if (name.endsWith('arm64-setup.exe')) {
|
||||||
|
updateData.platforms['windows-aarch64'].url = browser_download_url
|
||||||
|
}
|
||||||
|
// win arm signature
|
||||||
|
if (name.endsWith('arm64-setup.exe.sig')) {
|
||||||
|
const sig = await getSignature(browser_download_url)
|
||||||
|
updateData.platforms['windows-aarch64'].signature = sig
|
||||||
|
}
|
||||||
|
|
||||||
|
// darwin url (intel)
|
||||||
|
if (name.endsWith('.app.tar.gz') && !name.includes('aarch')) {
|
||||||
|
updateData.platforms.darwin.url = browser_download_url
|
||||||
|
updateData.platforms['darwin-intel'].url = browser_download_url
|
||||||
|
updateData.platforms['darwin-x86_64'].url = browser_download_url
|
||||||
|
}
|
||||||
|
// darwin signature (intel)
|
||||||
|
if (name.endsWith('.app.tar.gz.sig') && !name.includes('aarch')) {
|
||||||
|
const sig = await getSignature(browser_download_url)
|
||||||
|
updateData.platforms.darwin.signature = sig
|
||||||
|
updateData.platforms['darwin-intel'].signature = sig
|
||||||
|
updateData.platforms['darwin-x86_64'].signature = sig
|
||||||
|
}
|
||||||
|
|
||||||
|
// darwin url (aarch)
|
||||||
|
if (name.endsWith('aarch64.app.tar.gz')) {
|
||||||
|
updateData.platforms['darwin-aarch64'].url = browser_download_url
|
||||||
|
// 使linux可以检查更新
|
||||||
|
updateData.platforms.linux.url = browser_download_url
|
||||||
|
updateData.platforms['linux-x86_64'].url = browser_download_url
|
||||||
|
updateData.platforms['linux-x86'].url = browser_download_url
|
||||||
|
updateData.platforms['linux-i686'].url = browser_download_url
|
||||||
|
updateData.platforms['linux-aarch64'].url = browser_download_url
|
||||||
|
updateData.platforms['linux-armv7'].url = browser_download_url
|
||||||
|
}
|
||||||
|
// darwin signature (aarch)
|
||||||
|
if (name.endsWith('aarch64.app.tar.gz.sig')) {
|
||||||
|
const sig = await getSignature(browser_download_url)
|
||||||
|
updateData.platforms['darwin-aarch64'].signature = sig
|
||||||
|
updateData.platforms.linux.signature = sig
|
||||||
|
updateData.platforms['linux-x86_64'].signature = sig
|
||||||
|
updateData.platforms['linux-x86'].signature = sig
|
||||||
|
updateData.platforms['linux-i686'].signature = sig
|
||||||
|
updateData.platforms['linux-aarch64'].signature = sig
|
||||||
|
updateData.platforms['linux-armv7'].signature = sig
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.allSettled(promises)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove platforms without URLs, generate proxy data
|
||||||
|
function finalizeUpdateData(updateData) {
|
||||||
|
Object.entries(updateData.platforms).forEach(([key, value]) => {
|
||||||
|
if (!value.url) {
|
||||||
|
console.log(`[Error]: failed to parse release for "${key}"`)
|
||||||
|
delete updateData.platforms[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const proxyData = JSON.parse(JSON.stringify(updateData))
|
||||||
|
Object.entries(proxyData.platforms).forEach(([key, value]) => {
|
||||||
|
if (value.url) {
|
||||||
|
proxyData.platforms[key].url = 'https://update.hwdns.net/' + value.url
|
||||||
|
} else {
|
||||||
|
console.log(`[Error]: proxyData.platforms.${key} is null`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return proxyData
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Upload update JSON files to a release tag (creates release if not found)
|
||||||
|
async function uploadToRelease(
|
||||||
|
github,
|
||||||
|
options,
|
||||||
|
{ tagName, jsonFile, proxyFile, releaseName, releaseBody, prerelease },
|
||||||
|
updateData,
|
||||||
|
proxyData,
|
||||||
|
) {
|
||||||
|
let updateRelease
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await github.rest.repos.getReleaseByTag({
|
||||||
|
...options,
|
||||||
|
tag: tagName,
|
||||||
|
})
|
||||||
|
updateRelease = response.data
|
||||||
|
console.log(
|
||||||
|
`Found existing ${tagName} release with ID: ${updateRelease.id}`,
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
if (error.status === 404) {
|
||||||
|
console.log(`Release ${tagName} not found, creating...`)
|
||||||
|
const createResponse = await github.rest.repos.createRelease({
|
||||||
|
...options,
|
||||||
|
tag_name: tagName,
|
||||||
|
name: releaseName,
|
||||||
|
body: releaseBody,
|
||||||
|
prerelease: !!prerelease,
|
||||||
|
})
|
||||||
|
updateRelease = createResponse.data
|
||||||
|
console.log(`Created ${tagName} release with ID: ${updateRelease.id}`)
|
||||||
|
} else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = { owner: context.repo.owner, repo: context.repo.repo }
|
// Delete existing assets with matching names
|
||||||
const github = getOctokit(process.env.GITHUB_TOKEN)
|
for (const asset of updateRelease.assets) {
|
||||||
|
if (asset.name === jsonFile || asset.name === proxyFile) {
|
||||||
|
await github.rest.repos
|
||||||
|
.deleteReleaseAsset({ ...options, asset_id: asset.id })
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch all tags using pagination
|
// Upload new assets
|
||||||
|
await github.rest.repos.uploadReleaseAsset({
|
||||||
|
...options,
|
||||||
|
release_id: updateRelease.id,
|
||||||
|
name: jsonFile,
|
||||||
|
data: JSON.stringify(updateData, null, 2),
|
||||||
|
})
|
||||||
|
|
||||||
|
await github.rest.repos.uploadReleaseAsset({
|
||||||
|
...options,
|
||||||
|
release_id: updateRelease.id,
|
||||||
|
name: proxyFile,
|
||||||
|
data: JSON.stringify(proxyData, null, 2),
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`Successfully uploaded update files to ${tagName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Channel: stable (default) ──────────────────────────<E29480><E29480><EFBFBD>───────────────────
|
||||||
|
|
||||||
|
async function resolveStableChannel(github, options) {
|
||||||
|
// Fetch tags to find the latest stable release (vX.Y.Z)
|
||||||
let allTags = []
|
let allTags = []
|
||||||
let page = 1
|
let page = 1
|
||||||
const perPage = 100
|
const perPage = 100
|
||||||
@ -33,289 +210,226 @@ async function resolveUpdater() {
|
|||||||
per_page: perPage,
|
per_page: perPage,
|
||||||
page: page,
|
page: page,
|
||||||
})
|
})
|
||||||
|
|
||||||
allTags = allTags.concat(pageTags)
|
allTags = allTags.concat(pageTags)
|
||||||
|
if (pageTags.length < perPage) break
|
||||||
// Break if we received fewer tags than requested (last page)
|
|
||||||
if (pageTags.length < perPage) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
page++
|
page++
|
||||||
}
|
}
|
||||||
|
|
||||||
const tags = allTags
|
console.log(`Retrieved ${allTags.length} tags in total`)
|
||||||
console.log(`Retrieved ${tags.length} tags in total`)
|
|
||||||
|
|
||||||
// More flexible tag detection with regex patterns
|
const stableTag = allTags.find((t) => /^v\d+\.\d+\.\d+$/.test(t.name))
|
||||||
const stableTagRegex = /^v\d+\.\d+\.\d+$/ // Matches vX.Y.Z format
|
|
||||||
// const preReleaseRegex = /^v\d+\.\d+\.\d+-(alpha|beta|rc|pre)/i; // Matches vX.Y.Z-alpha/beta/rc format
|
|
||||||
const preReleaseRegex = /^(alpha|beta|rc|pre)$/i // Matches exact alpha/beta/rc/pre tags
|
|
||||||
|
|
||||||
// Get the latest stable tag and pre-release tag
|
|
||||||
const stableTag = tags.find((t) => stableTagRegex.test(t.name))
|
|
||||||
const preReleaseTag = tags.find((t) => preReleaseRegex.test(t.name))
|
|
||||||
|
|
||||||
console.log('All tags:', tags.map((t) => t.name).join(', '))
|
|
||||||
console.log('Stable tag:', stableTag ? stableTag.name : 'None found')
|
console.log('Stable tag:', stableTag ? stableTag.name : 'None found')
|
||||||
console.log(
|
|
||||||
'Pre-release tag:',
|
|
||||||
preReleaseTag ? preReleaseTag.name : 'None found',
|
|
||||||
)
|
|
||||||
console.log()
|
|
||||||
|
|
||||||
// Process stable release
|
if (!stableTag) {
|
||||||
if (stableTag) {
|
console.log('No stable tag found, nothing to do')
|
||||||
await processRelease(github, options, stableTag, false)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process pre-release if found
|
|
||||||
if (preReleaseTag) {
|
|
||||||
await processRelease(github, options, preReleaseTag, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process a release (stable or alpha) and generate update files
|
|
||||||
async function processRelease(github, options, tag, isAlpha) {
|
|
||||||
if (!tag) return
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data: release } = await github.rest.repos.getReleaseByTag({
|
const { data: release } = await github.rest.repos.getReleaseByTag({
|
||||||
...options,
|
...options,
|
||||||
tag: tag.name,
|
tag: stableTag.name,
|
||||||
})
|
})
|
||||||
|
|
||||||
const updateData = {
|
const updateData = {
|
||||||
name: tag.name,
|
name: stableTag.name,
|
||||||
notes: await resolveUpdateLog(tag.name).catch(() =>
|
notes: await resolveUpdateLog(stableTag.name).catch(() =>
|
||||||
resolveUpdateLogDefault().catch(() => 'No changelog available'),
|
resolveUpdateLogDefault().catch(() => 'No changelog available'),
|
||||||
),
|
),
|
||||||
pub_date: new Date().toISOString(),
|
pub_date: new Date().toISOString(),
|
||||||
platforms: {
|
platforms: buildPlatformData(),
|
||||||
win64: { signature: '', url: '' }, // compatible with older formats
|
|
||||||
linux: { signature: '', url: '' }, // compatible with older formats
|
|
||||||
darwin: { signature: '', url: '' }, // compatible with older formats
|
|
||||||
'darwin-aarch64': { signature: '', url: '' },
|
|
||||||
'darwin-intel': { signature: '', url: '' },
|
|
||||||
'darwin-x86_64': { signature: '', url: '' },
|
|
||||||
'linux-x86_64': { signature: '', url: '' },
|
|
||||||
'linux-x86': { signature: '', url: '' },
|
|
||||||
'linux-i686': { signature: '', url: '' },
|
|
||||||
'linux-aarch64': { signature: '', url: '' },
|
|
||||||
'linux-armv7': { signature: '', url: '' },
|
|
||||||
'windows-x86_64': { signature: '', url: '' },
|
|
||||||
'windows-aarch64': { signature: '', url: '' },
|
|
||||||
'windows-x86': { signature: '', url: '' },
|
|
||||||
'windows-i686': { signature: '', url: '' },
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises = release.assets.map(async (asset) => {
|
await processAssets(release, updateData)
|
||||||
const { name, browser_download_url } = asset
|
|
||||||
|
|
||||||
// Process all the platform URL and signature data
|
|
||||||
// win64 url
|
|
||||||
if (name.endsWith('x64-setup.exe')) {
|
|
||||||
updateData.platforms.win64.url = browser_download_url
|
|
||||||
updateData.platforms['windows-x86_64'].url = browser_download_url
|
|
||||||
}
|
|
||||||
// win64 signature
|
|
||||||
if (name.endsWith('x64-setup.exe.sig')) {
|
|
||||||
const sig = await getSignature(browser_download_url)
|
|
||||||
updateData.platforms.win64.signature = sig
|
|
||||||
updateData.platforms['windows-x86_64'].signature = sig
|
|
||||||
}
|
|
||||||
|
|
||||||
// win32 url
|
|
||||||
if (name.endsWith('x86-setup.exe')) {
|
|
||||||
updateData.platforms['windows-x86'].url = browser_download_url
|
|
||||||
updateData.platforms['windows-i686'].url = browser_download_url
|
|
||||||
}
|
|
||||||
// win32 signature
|
|
||||||
if (name.endsWith('x86-setup.exe.sig')) {
|
|
||||||
const sig = await getSignature(browser_download_url)
|
|
||||||
updateData.platforms['windows-x86'].signature = sig
|
|
||||||
updateData.platforms['windows-i686'].signature = sig
|
|
||||||
}
|
|
||||||
|
|
||||||
// win arm url
|
|
||||||
if (name.endsWith('arm64-setup.exe')) {
|
|
||||||
updateData.platforms['windows-aarch64'].url = browser_download_url
|
|
||||||
}
|
|
||||||
// win arm signature
|
|
||||||
if (name.endsWith('arm64-setup.exe.sig')) {
|
|
||||||
const sig = await getSignature(browser_download_url)
|
|
||||||
updateData.platforms['windows-aarch64'].signature = sig
|
|
||||||
}
|
|
||||||
|
|
||||||
// darwin url (intel)
|
|
||||||
if (name.endsWith('.app.tar.gz') && !name.includes('aarch')) {
|
|
||||||
updateData.platforms.darwin.url = browser_download_url
|
|
||||||
updateData.platforms['darwin-intel'].url = browser_download_url
|
|
||||||
updateData.platforms['darwin-x86_64'].url = browser_download_url
|
|
||||||
}
|
|
||||||
// darwin signature (intel)
|
|
||||||
if (name.endsWith('.app.tar.gz.sig') && !name.includes('aarch')) {
|
|
||||||
const sig = await getSignature(browser_download_url)
|
|
||||||
updateData.platforms.darwin.signature = sig
|
|
||||||
updateData.platforms['darwin-intel'].signature = sig
|
|
||||||
updateData.platforms['darwin-x86_64'].signature = sig
|
|
||||||
}
|
|
||||||
|
|
||||||
// darwin url (aarch)
|
|
||||||
if (name.endsWith('aarch64.app.tar.gz')) {
|
|
||||||
updateData.platforms['darwin-aarch64'].url = browser_download_url
|
|
||||||
// 使linux可以检查更新
|
|
||||||
updateData.platforms.linux.url = browser_download_url
|
|
||||||
updateData.platforms['linux-x86_64'].url = browser_download_url
|
|
||||||
updateData.platforms['linux-x86'].url = browser_download_url
|
|
||||||
updateData.platforms['linux-i686'].url = browser_download_url
|
|
||||||
updateData.platforms['linux-aarch64'].url = browser_download_url
|
|
||||||
updateData.platforms['linux-armv7'].url = browser_download_url
|
|
||||||
}
|
|
||||||
// darwin signature (aarch)
|
|
||||||
if (name.endsWith('aarch64.app.tar.gz.sig')) {
|
|
||||||
const sig = await getSignature(browser_download_url)
|
|
||||||
updateData.platforms['darwin-aarch64'].signature = sig
|
|
||||||
updateData.platforms.linux.signature = sig
|
|
||||||
updateData.platforms['linux-x86_64'].signature = sig
|
|
||||||
updateData.platforms['linux-x86'].url = browser_download_url
|
|
||||||
updateData.platforms['linux-i686'].url = browser_download_url
|
|
||||||
updateData.platforms['linux-aarch64'].signature = sig
|
|
||||||
updateData.platforms['linux-armv7'].signature = sig
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await Promise.allSettled(promises)
|
|
||||||
console.log(updateData)
|
console.log(updateData)
|
||||||
|
|
||||||
// maybe should test the signature as well
|
const proxyData = finalizeUpdateData(updateData)
|
||||||
// delete the null field
|
|
||||||
Object.entries(updateData.platforms).forEach(([key, value]) => {
|
|
||||||
if (!value.url) {
|
|
||||||
console.log(`[Error]: failed to parse release for "${key}"`)
|
|
||||||
delete updateData.platforms[key]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Generate a proxy update file for accelerated GitHub resources
|
await uploadToRelease(
|
||||||
const updateDataNew = JSON.parse(JSON.stringify(updateData))
|
github,
|
||||||
|
options,
|
||||||
Object.entries(updateDataNew.platforms).forEach(([key, value]) => {
|
{
|
||||||
if (value.url) {
|
tagName: 'updater',
|
||||||
updateDataNew.platforms[key].url =
|
jsonFile: 'update.json',
|
||||||
'https://update.hwdns.net/' + value.url
|
proxyFile: 'update-proxy.json',
|
||||||
} else {
|
releaseName: 'Auto-update Stable Channel',
|
||||||
console.log(`[Error]: updateDataNew.platforms.${key} is null`)
|
releaseBody:
|
||||||
}
|
'This release contains the update information for stable channel.',
|
||||||
})
|
prerelease: false,
|
||||||
|
},
|
||||||
// Get the appropriate updater release based on isAlpha flag
|
updateData,
|
||||||
const releaseTag = isAlpha ? ALPHA_TAG_NAME : UPDATE_TAG_NAME
|
proxyData,
|
||||||
console.log(
|
|
||||||
`Processing ${isAlpha ? 'alpha' : 'stable'} release:`,
|
|
||||||
releaseTag,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
try {
|
|
||||||
let updateRelease
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Try to get the existing release
|
|
||||||
const response = await github.rest.repos.getReleaseByTag({
|
|
||||||
...options,
|
|
||||||
tag: releaseTag,
|
|
||||||
})
|
|
||||||
updateRelease = response.data
|
|
||||||
console.log(
|
|
||||||
`Found existing ${releaseTag} release with ID: ${updateRelease.id}`,
|
|
||||||
)
|
|
||||||
} catch (error) {
|
|
||||||
// If release doesn't exist, create it
|
|
||||||
if (error.status === 404) {
|
|
||||||
console.log(
|
|
||||||
`Release with tag ${releaseTag} not found, creating new release...`,
|
|
||||||
)
|
|
||||||
const createResponse = await github.rest.repos.createRelease({
|
|
||||||
...options,
|
|
||||||
tag_name: releaseTag,
|
|
||||||
name: isAlpha
|
|
||||||
? 'Auto-update Alpha Channel'
|
|
||||||
: 'Auto-update Stable Channel',
|
|
||||||
body: `This release contains the update information for ${isAlpha ? 'alpha' : 'stable'} channel.`,
|
|
||||||
prerelease: isAlpha,
|
|
||||||
})
|
|
||||||
updateRelease = createResponse.data
|
|
||||||
console.log(
|
|
||||||
`Created new ${releaseTag} release with ID: ${updateRelease.id}`,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// If it's another error, throw it
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// File names based on release type
|
|
||||||
const jsonFile = isAlpha ? ALPHA_UPDATE_JSON_FILE : UPDATE_JSON_FILE
|
|
||||||
const proxyFile = isAlpha ? ALPHA_UPDATE_JSON_PROXY : UPDATE_JSON_PROXY
|
|
||||||
|
|
||||||
// Delete existing assets with these names
|
|
||||||
for (const asset of updateRelease.assets) {
|
|
||||||
if (asset.name === jsonFile) {
|
|
||||||
await github.rest.repos.deleteReleaseAsset({
|
|
||||||
...options,
|
|
||||||
asset_id: asset.id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (asset.name === proxyFile) {
|
|
||||||
await github.rest.repos
|
|
||||||
.deleteReleaseAsset({ ...options, asset_id: asset.id })
|
|
||||||
.catch(console.error) // do not break the pipeline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload new assets
|
|
||||||
await github.rest.repos.uploadReleaseAsset({
|
|
||||||
...options,
|
|
||||||
release_id: updateRelease.id,
|
|
||||||
name: jsonFile,
|
|
||||||
data: JSON.stringify(updateData, null, 2),
|
|
||||||
})
|
|
||||||
|
|
||||||
await github.rest.repos.uploadReleaseAsset({
|
|
||||||
...options,
|
|
||||||
release_id: updateRelease.id,
|
|
||||||
name: proxyFile,
|
|
||||||
data: JSON.stringify(updateDataNew, null, 2),
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`Successfully uploaded ${isAlpha ? 'alpha' : 'stable'} update files to ${releaseTag}`,
|
|
||||||
)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
`Failed to process ${isAlpha ? 'alpha' : 'stable'} release:`,
|
|
||||||
error.message,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.status === 404) {
|
if (error.status === 404) {
|
||||||
console.log(`Release not found for tag: ${tag.name}, skipping...`)
|
console.log(`Release not found for tag: ${stableTag.name}, skipping...`)
|
||||||
} else {
|
} else {
|
||||||
console.error(`Failed to get release for tag: ${tag.name}`, error.message)
|
console.error(
|
||||||
|
`Failed to get release for tag: ${stableTag.name}`,
|
||||||
|
error.message,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the signature file content
|
// ─── Channel: autobuild ─────────────────────────────────────────────────────
|
||||||
async function getSignature(url) {
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: { 'Content-Type': 'application/octet-stream' },
|
|
||||||
})
|
|
||||||
|
|
||||||
return response.text()
|
function parseBaseVersion(version) {
|
||||||
|
if (!version) return null
|
||||||
|
const match = version.replace(/^v/i, '').match(/^(\d+)\.(\d+)\.(\d+)/)
|
||||||
|
if (!match) return null
|
||||||
|
return [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])]
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveUpdater().catch(console.error)
|
function compareBase(a, b) {
|
||||||
|
if (!a || !b) return 0
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
if (a[i] > b[i]) return 1
|
||||||
|
if (a[i] < b[i]) return -1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractVersionFromAssets(assets) {
|
||||||
|
for (const asset of assets) {
|
||||||
|
const match = asset.name.match(
|
||||||
|
/Clash[._]Verge[_-]([\d]+\.[\d]+\.[\d]+(?:[+][^\s_]+)?)/,
|
||||||
|
)
|
||||||
|
if (match) return match[1]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveAutobuildChannel(github, options) {
|
||||||
|
// 1. Get autobuild release
|
||||||
|
let autobuildRelease = null
|
||||||
|
try {
|
||||||
|
const { data } = await github.rest.repos.getReleaseByTag({
|
||||||
|
...options,
|
||||||
|
tag: 'autobuild',
|
||||||
|
})
|
||||||
|
autobuildRelease = data
|
||||||
|
} catch (error) {
|
||||||
|
if (error.status === 404) {
|
||||||
|
console.log('No autobuild release found')
|
||||||
|
} else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Get latest stable release (vX.Y.Z tag)
|
||||||
|
let stableRelease = null
|
||||||
|
let stableTag = null
|
||||||
|
const { data: tags } = await github.rest.repos.listTags({
|
||||||
|
...options,
|
||||||
|
per_page: 20,
|
||||||
|
})
|
||||||
|
stableTag = tags.find((t) => /^v\d+\.\d+\.\d+$/.test(t.name))
|
||||||
|
|
||||||
|
if (stableTag) {
|
||||||
|
try {
|
||||||
|
const { data } = await github.rest.repos.getReleaseByTag({
|
||||||
|
...options,
|
||||||
|
tag: stableTag.name,
|
||||||
|
})
|
||||||
|
stableRelease = data
|
||||||
|
} catch (error) {
|
||||||
|
if (error.status !== 404) throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Compare base versions — stable wins only if strictly higher
|
||||||
|
const autobuildVersion = autobuildRelease
|
||||||
|
? extractVersionFromAssets(autobuildRelease.assets)
|
||||||
|
: null
|
||||||
|
const stableVersion = stableTag?.name?.replace(/^v/, '') ?? null
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Autobuild version: ${autobuildVersion} (base: ${parseBaseVersion(autobuildVersion)})`,
|
||||||
|
)
|
||||||
|
console.log(
|
||||||
|
`Stable version: ${stableVersion} (base: ${parseBaseVersion(stableVersion)})`,
|
||||||
|
)
|
||||||
|
|
||||||
|
let useRelease, useVersion
|
||||||
|
const cmp = compareBase(
|
||||||
|
parseBaseVersion(stableVersion),
|
||||||
|
parseBaseVersion(autobuildVersion),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (cmp > 0 && stableRelease) {
|
||||||
|
console.log('→ Using stable release (higher base version)')
|
||||||
|
useRelease = stableRelease
|
||||||
|
useVersion = stableVersion
|
||||||
|
} else if (autobuildRelease && autobuildVersion) {
|
||||||
|
console.log('→ Using autobuild release')
|
||||||
|
useRelease = autobuildRelease
|
||||||
|
useVersion = autobuildVersion
|
||||||
|
} else if (stableRelease) {
|
||||||
|
console.log('→ Falling back to stable release (no autobuild available)')
|
||||||
|
useRelease = stableRelease
|
||||||
|
useVersion = stableVersion
|
||||||
|
} else {
|
||||||
|
console.log('No releases found, nothing to do')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Build update data
|
||||||
|
const notes = await resolveUpdateLogDefault().catch(
|
||||||
|
() =>
|
||||||
|
'More new features are now supported. Check release page for details.',
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateData = {
|
||||||
|
version: useVersion,
|
||||||
|
name: useVersion,
|
||||||
|
notes,
|
||||||
|
pub_date: new Date().toISOString(),
|
||||||
|
platforms: buildPlatformData(),
|
||||||
|
}
|
||||||
|
|
||||||
|
await processAssets(useRelease, updateData)
|
||||||
|
console.log(updateData)
|
||||||
|
|
||||||
|
const proxyData = finalizeUpdateData(updateData)
|
||||||
|
|
||||||
|
await uploadToRelease(
|
||||||
|
github,
|
||||||
|
options,
|
||||||
|
{
|
||||||
|
tagName: 'updater-autobuild',
|
||||||
|
jsonFile: 'update.json',
|
||||||
|
proxyFile: 'update-proxy.json',
|
||||||
|
releaseName: 'Auto-update AutoBuild Channel',
|
||||||
|
releaseBody:
|
||||||
|
'This release contains the update information for the AutoBuild channel.',
|
||||||
|
prerelease: true,
|
||||||
|
},
|
||||||
|
updateData,
|
||||||
|
proxyData,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Main ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
if (!process.env.GITHUB_TOKEN) {
|
||||||
|
throw new Error('GITHUB_TOKEN is required')
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = { owner: context.repo.owner, repo: context.repo.repo }
|
||||||
|
const github = getOctokit(process.env.GITHUB_TOKEN)
|
||||||
|
|
||||||
|
const channel = process.argv[2]
|
||||||
|
|
||||||
|
if (channel === 'autobuild') {
|
||||||
|
console.log('=== Resolving autobuild channel ===')
|
||||||
|
await resolveAutobuildChannel(github, options)
|
||||||
|
} else {
|
||||||
|
console.log('=== Resolving stable channel ===')
|
||||||
|
await resolveStableChannel(github, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error)
|
||||||
|
|||||||
@ -134,7 +134,11 @@ const localVersionNormalized = normalizeVersion(appVersion)
|
|||||||
export const checkUpdateSafe = async (
|
export const checkUpdateSafe = async (
|
||||||
options?: CheckOptions,
|
options?: CheckOptions,
|
||||||
): Promise<Update | null> => {
|
): Promise<Update | null> => {
|
||||||
const result = await check({ ...(options ?? {}), allowDowngrades: false })
|
// allowDowngrades: true so the Tauri plugin always returns an Update object.
|
||||||
|
// Semver ignores build metadata (+autobuild.MMDD.hash), so the plugin would
|
||||||
|
// treat same-base autobuild versions as equal and return null.
|
||||||
|
// Our custom compareVersions below handles the actual upgrade decision.
|
||||||
|
const result = await check({ ...(options ?? {}), allowDowngrades: true })
|
||||||
if (!result) return null
|
if (!result) return null
|
||||||
|
|
||||||
const remoteVersion = resolveRemoteVersion(result)
|
const remoteVersion = resolveRemoteVersion(result)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user