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
|
||||
run: pnpm release-version autobuild-latest
|
||||
|
||||
- name: Override updater endpoints for autobuild
|
||||
run: pnpm override-updater-endpoints autobuild
|
||||
|
||||
- name: Add Rust Target
|
||||
run: |
|
||||
# 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
|
||||
run: pnpm release-version autobuild-latest
|
||||
|
||||
- name: Override updater endpoints for autobuild
|
||||
run: pnpm override-updater-endpoints autobuild
|
||||
|
||||
- name: 'Setup for linux'
|
||||
run: |-
|
||||
sudo ls -lR /etc/apt/
|
||||
@ -449,6 +455,9 @@ jobs:
|
||||
- name: Release ${{ env.TAG_CHANNEL }} Version
|
||||
run: pnpm release-version autobuild-latest
|
||||
|
||||
- name: Override updater endpoints for autobuild
|
||||
run: pnpm override-updater-endpoints autobuild
|
||||
|
||||
- name: Download WebView2 Runtime
|
||||
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
|
||||
@ -510,6 +519,37 @@ jobs:
|
||||
env:
|
||||
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:
|
||||
name: Notify Telegram
|
||||
runs-on: ubuntu-latest
|
||||
@ -519,6 +559,7 @@ jobs:
|
||||
autobuild-x86-windows-macos-linux,
|
||||
autobuild-arm-linux,
|
||||
autobuild-x86-arm-windows_webview2,
|
||||
release-update-autobuild,
|
||||
]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
||||
@ -16,7 +16,9 @@
|
||||
"web:serve": "vite preview",
|
||||
"prebuild": "node scripts/prebuild.mjs",
|
||||
"updater": "node scripts/updater.mjs",
|
||||
"updater-autobuild": "node scripts/updater.mjs autobuild",
|
||||
"updater-fixed-webview2": "node scripts/updater-fixed-webview2.mjs",
|
||||
"override-updater-endpoints": "node scripts/override-updater-endpoints.mjs",
|
||||
"portable": "node scripts/portable.mjs",
|
||||
"portable-fixed-webview2": "node scripts/portable-fixed-webview2.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'
|
||||
|
||||
// Add stable update JSON filenames
|
||||
const UPDATE_TAG_NAME = 'updater'
|
||||
const UPDATE_JSON_FILE = 'update.json'
|
||||
const UPDATE_JSON_PROXY = 'update-proxy.json'
|
||||
// Add alpha update JSON filenames
|
||||
const ALPHA_TAG_NAME = 'updater-alpha'
|
||||
const ALPHA_UPDATE_JSON_FILE = 'update.json'
|
||||
const ALPHA_UPDATE_JSON_PROXY = 'update-proxy.json'
|
||||
/// get the signature file content
|
||||
async function getSignature(url) {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/octet-stream' },
|
||||
})
|
||||
return response.text()
|
||||
}
|
||||
|
||||
/// generate update.json
|
||||
/// upload to update tag's release asset
|
||||
async function resolveUpdater() {
|
||||
if (process.env.GITHUB_TOKEN === undefined) {
|
||||
throw new Error('GITHUB_TOKEN is required')
|
||||
function buildPlatformData() {
|
||||
return {
|
||||
win64: { signature: '', url: '' },
|
||||
linux: { signature: '', url: '' },
|
||||
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 }
|
||||
const github = getOctokit(process.env.GITHUB_TOKEN)
|
||||
// Delete existing assets with matching names
|
||||
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 page = 1
|
||||
const perPage = 100
|
||||
@ -33,289 +210,226 @@ async function resolveUpdater() {
|
||||
per_page: perPage,
|
||||
page: page,
|
||||
})
|
||||
|
||||
allTags = allTags.concat(pageTags)
|
||||
|
||||
// Break if we received fewer tags than requested (last page)
|
||||
if (pageTags.length < perPage) {
|
||||
break
|
||||
}
|
||||
|
||||
if (pageTags.length < perPage) break
|
||||
page++
|
||||
}
|
||||
|
||||
const tags = allTags
|
||||
console.log(`Retrieved ${tags.length} tags in total`)
|
||||
console.log(`Retrieved ${allTags.length} tags in total`)
|
||||
|
||||
// More flexible tag detection with regex patterns
|
||||
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
|
||||
const stableTag = allTags.find((t) => /^v\d+\.\d+\.\d+$/.test(t.name))
|
||||
|
||||
// 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(
|
||||
'Pre-release tag:',
|
||||
preReleaseTag ? preReleaseTag.name : 'None found',
|
||||
)
|
||||
console.log()
|
||||
|
||||
// Process stable release
|
||||
if (stableTag) {
|
||||
await processRelease(github, options, stableTag, false)
|
||||
if (!stableTag) {
|
||||
console.log('No stable tag found, nothing to do')
|
||||
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 {
|
||||
const { data: release } = await github.rest.repos.getReleaseByTag({
|
||||
...options,
|
||||
tag: tag.name,
|
||||
tag: stableTag.name,
|
||||
})
|
||||
|
||||
const updateData = {
|
||||
name: tag.name,
|
||||
notes: await resolveUpdateLog(tag.name).catch(() =>
|
||||
name: stableTag.name,
|
||||
notes: await resolveUpdateLog(stableTag.name).catch(() =>
|
||||
resolveUpdateLogDefault().catch(() => 'No changelog available'),
|
||||
),
|
||||
pub_date: new Date().toISOString(),
|
||||
platforms: {
|
||||
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: '' },
|
||||
},
|
||||
platforms: buildPlatformData(),
|
||||
}
|
||||
|
||||
const promises = release.assets.map(async (asset) => {
|
||||
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)
|
||||
await processAssets(release, updateData)
|
||||
console.log(updateData)
|
||||
|
||||
// maybe should test the signature as well
|
||||
// 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]
|
||||
}
|
||||
})
|
||||
const proxyData = finalizeUpdateData(updateData)
|
||||
|
||||
// Generate a proxy update file for accelerated GitHub resources
|
||||
const updateDataNew = JSON.parse(JSON.stringify(updateData))
|
||||
|
||||
Object.entries(updateDataNew.platforms).forEach(([key, value]) => {
|
||||
if (value.url) {
|
||||
updateDataNew.platforms[key].url =
|
||||
'https://update.hwdns.net/' + value.url
|
||||
} else {
|
||||
console.log(`[Error]: updateDataNew.platforms.${key} is null`)
|
||||
}
|
||||
})
|
||||
|
||||
// Get the appropriate updater release based on isAlpha flag
|
||||
const releaseTag = isAlpha ? ALPHA_TAG_NAME : UPDATE_TAG_NAME
|
||||
console.log(
|
||||
`Processing ${isAlpha ? 'alpha' : 'stable'} release:`,
|
||||
releaseTag,
|
||||
await uploadToRelease(
|
||||
github,
|
||||
options,
|
||||
{
|
||||
tagName: 'updater',
|
||||
jsonFile: 'update.json',
|
||||
proxyFile: 'update-proxy.json',
|
||||
releaseName: 'Auto-update Stable Channel',
|
||||
releaseBody:
|
||||
'This release contains the update information for stable channel.',
|
||||
prerelease: false,
|
||||
},
|
||||
updateData,
|
||||
proxyData,
|
||||
)
|
||||
|
||||
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) {
|
||||
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 {
|
||||
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
|
||||
async function getSignature(url) {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/octet-stream' },
|
||||
})
|
||||
// ─── Channel: autobuild ─────────────────────────────────────────────────────
|
||||
|
||||
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 (
|
||||
options?: CheckOptions,
|
||||
): 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
|
||||
|
||||
const remoteVersion = resolveRemoteVersion(result)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user