diff --git a/.github/workflows/autobuild.yml b/.github/workflows/autobuild.yml index d089f1a1c..f793e849e 100644 --- a/.github/workflows/autobuild.yml +++ b/.github/workflows/autobuild.yml @@ -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 diff --git a/package.json b/package.json index 8d98cf12f..565794455 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/override-updater-endpoints.mjs b/scripts/override-updater-endpoints.mjs new file mode 100644 index 000000000..241595b1d --- /dev/null +++ b/scripts/override-updater-endpoints.mjs @@ -0,0 +1,32 @@ +/** + * Override updater endpoints in tauri.conf.json for a specific channel. + * + * Usage: + * node scripts/override-updater-endpoints.mjs + * + * 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 ') + 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}`)) diff --git a/scripts/updater.mjs b/scripts/updater.mjs index c7bc65a89..87624f141 100644 --- a/scripts/updater.mjs +++ b/scripts/updater.mjs @@ -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) ──────────────────────────���─────────────────── + +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) diff --git a/src/services/update.ts b/src/services/update.ts index f270c176e..dfd6d248e 100644 --- a/src/services/update.ts +++ b/src/services/update.ts @@ -134,7 +134,11 @@ const localVersionNormalized = normalizeVersion(appVersion) export const checkUpdateSafe = async ( options?: CheckOptions, ): Promise => { - 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)