fix: harden alpha updater metadata lookup
This commit is contained in:
41
.github/workflows/release-stable.yml
vendored
41
.github/workflows/release-stable.yml
vendored
@@ -521,6 +521,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -767,9 +768,13 @@ jobs:
|
||||
cp -R site/.vitepress/dist/. _site/
|
||||
if [ -d release-notes ]; then cp release-notes/*.md _site/release-notes/ 2>/dev/null || true; fi
|
||||
gh api -H "Accept: application/vnd.github.html+json" repos/${{ github.repository }}/releases --paginate > _site/releases.json
|
||||
PAGES_URL="https://refactoringhq.github.io/${GITHUB_REPOSITORY#*/}"
|
||||
ALPHA_TAG=$(gh release list --repo ${{ github.repository }} --exclude-drafts --limit 100 --json tagName,isPrerelease --jq '[.[] | select(.isPrerelease and (.tagName | test("^alpha-v[0-9]+\\.[0-9]+\\.[0-9]+-alpha\\.[0-9]+$")))][0].tagName // ""')
|
||||
|
||||
curl -fsSL "${PAGES_URL}/alpha/latest.json" -o _site/alpha/latest.json || echo '{}' > _site/alpha/latest.json
|
||||
if [ -n "$ALPHA_TAG" ]; then
|
||||
gh release download --repo ${{ github.repository }} "$ALPHA_TAG" --pattern "alpha-latest.json" --output _site/alpha/latest.json || echo '{}' > _site/alpha/latest.json
|
||||
else
|
||||
echo '{}' > _site/alpha/latest.json
|
||||
fi
|
||||
gh release download --repo ${{ github.repository }} "${{ needs.version.outputs.tag }}" --pattern "stable-latest.json" --output _site/stable/latest.json || echo '{}' > _site/stable/latest.json
|
||||
bun scripts/build-release-download-page.ts --latest-json _site/stable/latest.json --releases-json _site/releases.json --output-file _site/stable/download/index.html
|
||||
bun scripts/build-release-history-page.ts --releases-json _site/releases.json --output-file _site/releases/index.html
|
||||
@@ -785,3 +790,35 @@ jobs:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./_site
|
||||
commit_message: "Update release history for ${{ needs.version.outputs.tag }}"
|
||||
|
||||
- name: Request and verify GitHub Pages rebuild
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
BUILD_URL=$(gh api --method POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"repos/${{ github.repository }}/pages/builds" \
|
||||
--jq '.url')
|
||||
BUILD_ID="${BUILD_URL##*/}"
|
||||
if [ -z "$BUILD_ID" ]; then
|
||||
echo "::error::GitHub Pages rebuild request did not return a build id."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for attempt in {1..30}; do
|
||||
STATUS=$(gh api "repos/${{ github.repository }}/pages/builds/${BUILD_ID}" --jq '.status')
|
||||
echo "GitHub Pages build ${BUILD_ID} status: ${STATUS} (attempt ${attempt}/30)"
|
||||
case "$STATUS" in
|
||||
built)
|
||||
exit 0
|
||||
;;
|
||||
errored)
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
sleep 10
|
||||
done
|
||||
|
||||
echo "::error::GitHub Pages build ${BUILD_ID} did not finish within 5 minutes."
|
||||
exit 1
|
||||
|
||||
41
.github/workflows/release.yml
vendored
41
.github/workflows/release.yml
vendored
@@ -577,6 +577,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -813,10 +814,14 @@ jobs:
|
||||
cp -R site/.vitepress/dist/. _site/
|
||||
if [ -d release-notes ]; then cp release-notes/*.md _site/release-notes/ 2>/dev/null || true; fi
|
||||
gh api -H "Accept: application/vnd.github.html+json" repos/${{ github.repository }}/releases --paginate > _site/releases.json
|
||||
PAGES_URL="https://refactoringhq.github.io/${GITHUB_REPOSITORY#*/}"
|
||||
STABLE_TAG=$(gh release list --repo ${{ github.repository }} --exclude-drafts --exclude-pre-releases --limit 1 --json tagName --jq '.[0].tagName // ""')
|
||||
|
||||
gh release download --repo ${{ github.repository }} "${{ needs.version.outputs.tag }}" --pattern "alpha-latest.json" --output _site/alpha/latest.json || echo '{}' > _site/alpha/latest.json
|
||||
curl -fsSL "${PAGES_URL}/stable/latest.json" -o _site/stable/latest.json || echo '{}' > _site/stable/latest.json
|
||||
if [ -n "$STABLE_TAG" ]; then
|
||||
gh release download --repo ${{ github.repository }} "$STABLE_TAG" --pattern "stable-latest.json" --output _site/stable/latest.json || echo '{}' > _site/stable/latest.json
|
||||
else
|
||||
echo '{}' > _site/stable/latest.json
|
||||
fi
|
||||
bun scripts/build-release-download-page.ts --latest-json _site/stable/latest.json --releases-json _site/releases.json --output-file _site/stable/download/index.html
|
||||
bun scripts/build-release-history-page.ts --releases-json _site/releases.json --output-file _site/releases/index.html
|
||||
mkdir -p _site/download
|
||||
@@ -831,3 +836,35 @@ jobs:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./_site
|
||||
commit_message: "Update release history for ${{ needs.version.outputs.tag }}"
|
||||
|
||||
- name: Request and verify GitHub Pages rebuild
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
BUILD_URL=$(gh api --method POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"repos/${{ github.repository }}/pages/builds" \
|
||||
--jq '.url')
|
||||
BUILD_ID="${BUILD_URL##*/}"
|
||||
if [ -z "$BUILD_ID" ]; then
|
||||
echo "::error::GitHub Pages rebuild request did not return a build id."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for attempt in {1..30}; do
|
||||
STATUS=$(gh api "repos/${{ github.repository }}/pages/builds/${BUILD_ID}" --jq '.status')
|
||||
echo "GitHub Pages build ${BUILD_ID} status: ${STATUS} (attempt ${attempt}/30)"
|
||||
case "$STATUS" in
|
||||
built)
|
||||
exit 0
|
||||
;;
|
||||
errored)
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
sleep 10
|
||||
done
|
||||
|
||||
echo "::error::GitHub Pages build ${BUILD_ID} did not finish within 5 minutes."
|
||||
exit 1
|
||||
|
||||
@@ -885,7 +885,7 @@ Managed by `useSettings` hook and `SettingsPanel` component. `theme_mode` is ins
|
||||
- **`src/lib/appUpdater.ts`** — Thin wrapper around the Tauri updater commands. Keeps the React hook free of endpoint-selection details.
|
||||
|
||||
### Rust
|
||||
- **`src-tauri/src/app_updater.rs`** — Chooses the correct update endpoint (`alpha/latest.json` or `stable/latest.json`) and adapts Tauri updater results into frontend-friendly payloads.
|
||||
- **`src-tauri/src/app_updater.rs`** — Chooses the correct update endpoint and adapts Tauri updater results into frontend-friendly payloads. Stable uses the public `stable/latest.json` feed. Alpha first resolves the newest non-draft `alpha-vYYYY.M.D-alpha.NNNN` GitHub Release asset named `alpha-latest.json`, then falls back to the public `alpha/latest.json` feed if the release lookup is unavailable.
|
||||
- **`src-tauri/src/commands/version.rs`** — Formats app build/version labels for the status bar, including calendar alpha labels and legacy release compatibility.
|
||||
|
||||
### Tauri Commands
|
||||
@@ -893,6 +893,6 @@ Managed by `useSettings` hook and `SettingsPanel` component. `theme_mode` is ins
|
||||
- **`download_and_install_app_update`** — Channel-aware download/install with streamed progress events.
|
||||
|
||||
### CI/CD
|
||||
- **`.github/workflows/release.yml`** — Alpha prereleases from every push to `main` using calendar-semver technical versions (`YYYY.M.D-alpha.N`) and clean `Alpha YYYY.M.D.N` release names. GitHub alpha tags zero-pad the prerelease sequence (`alpha-vYYYY.M.D-alpha.NNNN`) so GitHub release ordering stays chronological while the shipped app version remains `YYYY.M.D-alpha.N`. Publishes `alpha/latest.json` with macOS Apple Silicon/Intel, Linux x64, and Windows x64 updater entries, then refreshes the legacy `latest.json` / `latest-canary.json` aliases to the alpha feed. macOS release assets use `Tolaria_<version>_macOS_Silicon` and `Tolaria_<version>_macOS_Intel` base names. Packaged builds pass the computed version as `VITE_SENTRY_RELEASE`, which is retained as a diagnostic build-version tag but not registered as a normal Sentry release for alpha builds.
|
||||
- **`.github/workflows/release-stable.yml`** — Stable releases from `stable-vYYYY.M.D` tags. Publishes `stable/latest.json`, macOS Apple Silicon and Intel DMG/updater artifacts, Windows x64 installers/updater bundles, Linux x86_64 `.deb` / AppImage artifacts, and a static public download page that starts the selected installer without replacing the page with a blank download navigation. Stable macOS DMG/updater assets use the same `Tolaria_<version>_macOS_Silicon` and `Tolaria_<version>_macOS_Intel` base names. Packaged builds pass the computed stable version as `VITE_SENTRY_RELEASE`, which is registered as Sentry's release.
|
||||
- **`.github/workflows/release.yml`** — Alpha prereleases from every push to `main` using calendar-semver technical versions (`YYYY.M.D-alpha.N`) and clean `Alpha YYYY.M.D.N` release names. GitHub alpha tags zero-pad the prerelease sequence (`alpha-vYYYY.M.D-alpha.NNNN`) so GitHub release ordering stays chronological while the shipped app version remains `YYYY.M.D-alpha.N`. Publishes `alpha/latest.json` with macOS Apple Silicon/Intel, Linux x64, and Windows x64 updater entries, then refreshes the legacy `latest.json` / `latest-canary.json` aliases to the alpha feed. The docs/release Pages job reads the stable manifest from the latest stable release asset instead of copying the live Pages URL, then explicitly requests and verifies a GitHub Pages rebuild after deploying `gh-pages`. macOS release assets use `Tolaria_<version>_macOS_Silicon` and `Tolaria_<version>_macOS_Intel` base names. Packaged builds pass the computed version as `VITE_SENTRY_RELEASE`, which is retained as a diagnostic build-version tag but not registered as a normal Sentry release for alpha builds.
|
||||
- **`.github/workflows/release-stable.yml`** — Stable releases from `stable-vYYYY.M.D` tags. Publishes `stable/latest.json`, macOS Apple Silicon and Intel DMG/updater artifacts, Windows x64 installers/updater bundles, Linux x86_64 `.deb` / AppImage artifacts, and a static public download page that starts the selected installer without replacing the page with a blank download navigation. The Pages job reads the alpha manifest from the latest alpha release asset instead of copying the live Pages URL, then explicitly requests and verifies a GitHub Pages rebuild after deploying `gh-pages`. Stable macOS DMG/updater assets use the same `Tolaria_<version>_macOS_Silicon` and `Tolaria_<version>_macOS_Intel` base names. Packaged builds pass the computed stable version as `VITE_SENTRY_RELEASE`, which is registered as Sentry's release.
|
||||
- **Beta cohorts** are handled in PostHog targeting only. There is no beta updater feed.
|
||||
|
||||
@@ -214,7 +214,7 @@ tolaria/
|
||||
│ │ ├── codex_cli.rs # Codex CLI adapter
|
||||
│ │ ├── pi_cli.rs # Pi CLI adapter
|
||||
│ │ ├── mcp.rs # MCP server lifecycle + explicit config registration/removal
|
||||
│ │ ├── app_updater.rs # Alpha/stable updater endpoint selection
|
||||
│ │ ├── app_updater.rs # Alpha/stable updater metadata resolution
|
||||
│ │ ├── settings.rs # App settings persistence
|
||||
│ │ ├── vault_config.rs # Per-vault UI config
|
||||
│ │ ├── vault_list.rs # Vault list persistence
|
||||
@@ -286,7 +286,7 @@ tolaria/
|
||||
| `src-tauri/src/ai_agents.rs` | CLI-agent request normalization, availability aggregation, adapter dispatch, and Claude event mapping. |
|
||||
| `src-tauri/src/cli_agent_runtime.rs` | Shared CLI-agent request shape, prompt wrapping, JSON subprocess lifecycle, version probing, and MCP path helpers. |
|
||||
| `src-tauri/src/claude_cli.rs`, `src-tauri/src/codex_cli.rs`, `src-tauri/src/opencode_cli.rs`, `src-tauri/src/pi_cli.rs`, `src-tauri/src/gemini_cli.rs` | Per-agent command, config, discovery, and JSON event adapters. |
|
||||
| `src-tauri/src/app_updater.rs` | Desktop updater bridge — selects alpha/stable manifests and streams install progress. |
|
||||
| `src-tauri/src/app_updater.rs` | Desktop updater bridge — resolves alpha/stable manifests and streams install progress. |
|
||||
|
||||
### Editor
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use tauri::{ipc::Channel, AppHandle, Runtime, Url};
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
|
||||
const ALPHA_METADATA_ASSET_NAME: &str = "alpha-latest.json";
|
||||
const GITHUB_RELEASES_API_URL: &str =
|
||||
"https://api.github.com/repos/refactoringhq/tolaria/releases?per_page=100";
|
||||
const RELEASES_BASE_URL: &str = "https://refactoringhq.github.io/tolaria";
|
||||
const UPDATER_HTTP_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
const UPDATER_USER_AGENT: &str = concat!("Tolaria/", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -33,6 +39,27 @@ enum ReleaseChannel {
|
||||
Stable,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct AlphaReleaseVersion {
|
||||
year: i32,
|
||||
month: u32,
|
||||
day: u32,
|
||||
sequence: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct GitHubRelease {
|
||||
tag_name: String,
|
||||
draft: bool,
|
||||
assets: Vec<GitHubAsset>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct GitHubAsset {
|
||||
name: String,
|
||||
browser_download_url: String,
|
||||
}
|
||||
|
||||
impl ReleaseChannel {
|
||||
fn from_settings_value(value: Option<&str>) -> Self {
|
||||
match crate::settings::effective_release_channel(value) {
|
||||
@@ -54,13 +81,94 @@ impl ReleaseChannel {
|
||||
}
|
||||
}
|
||||
|
||||
impl AlphaReleaseVersion {
|
||||
fn parse_tag(tag_name: &str) -> Option<Self> {
|
||||
let release = tag_name.strip_prefix("alpha-v")?;
|
||||
let (date, sequence) = release.split_once("-alpha.")?;
|
||||
let sequence = sequence.parse().ok()?;
|
||||
let (year, month, day) = parse_calendar_date(date)?;
|
||||
chrono::NaiveDate::from_ymd_opt(year, month, day)?;
|
||||
|
||||
Some(Self {
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
sequence,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_calendar_date(value: &str) -> Option<(i32, u32, u32)> {
|
||||
let mut parts = value.split('.');
|
||||
let year = parts.next()?.parse().ok()?;
|
||||
let month = parts.next()?.parse().ok()?;
|
||||
let day = parts.next()?.parse().ok()?;
|
||||
if parts.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((year, month, day))
|
||||
}
|
||||
|
||||
fn latest_alpha_release_metadata_url(releases: &[GitHubRelease]) -> Option<Url> {
|
||||
releases
|
||||
.iter()
|
||||
.filter(|release| !release.draft)
|
||||
.filter_map(alpha_release_metadata_candidate)
|
||||
.max_by_key(|(version, _)| *version)
|
||||
.map(|(_, url)| url)
|
||||
}
|
||||
|
||||
fn alpha_release_metadata_candidate(release: &GitHubRelease) -> Option<(AlphaReleaseVersion, Url)> {
|
||||
let version = AlphaReleaseVersion::parse_tag(&release.tag_name)?;
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == ALPHA_METADATA_ASSET_NAME)?;
|
||||
let url = Url::parse(&asset.browser_download_url).ok()?;
|
||||
|
||||
Some((version, url))
|
||||
}
|
||||
|
||||
async fn alpha_release_metadata_endpoint() -> Result<Url, String> {
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(UPDATER_HTTP_TIMEOUT)
|
||||
.user_agent(UPDATER_USER_AGENT)
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to create updater metadata client: {e}"))?;
|
||||
|
||||
let releases = client
|
||||
.get(GITHUB_RELEASES_API_URL)
|
||||
.header(reqwest::header::ACCEPT, "application/vnd.github+json")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch GitHub releases: {e}"))?
|
||||
.error_for_status()
|
||||
.map_err(|e| format!("GitHub releases request failed: {e}"))?
|
||||
.json::<Vec<GitHubRelease>>()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse GitHub releases: {e}"))?;
|
||||
|
||||
latest_alpha_release_metadata_url(&releases)
|
||||
.ok_or_else(|| "No alpha updater metadata asset found in GitHub releases".to_string())
|
||||
}
|
||||
|
||||
async fn updater_endpoint(release_channel: ReleaseChannel) -> Result<Url, String> {
|
||||
match release_channel {
|
||||
ReleaseChannel::Stable => release_channel.updater_endpoint(),
|
||||
ReleaseChannel::Alpha => alpha_release_metadata_endpoint()
|
||||
.await
|
||||
.or_else(|_| release_channel.updater_endpoint()),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_updater<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
release_channel: ReleaseChannel,
|
||||
endpoint: Url,
|
||||
) -> Result<tauri_plugin_updater::Updater, String> {
|
||||
app_handle
|
||||
.updater_builder()
|
||||
.endpoints(vec![release_channel.updater_endpoint()?])
|
||||
.endpoints(vec![endpoint])
|
||||
.map_err(|e| format!("Failed to configure updater endpoint: {e}"))?
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to build updater: {e}"))
|
||||
@@ -75,41 +183,24 @@ fn to_update_metadata(update: tauri_plugin_updater::Update) -> AppUpdateMetadata
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn check_for_app_update<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
release_channel: Option<String>,
|
||||
) -> Result<Option<AppUpdateMetadata>, String> {
|
||||
let channel = ReleaseChannel::from_settings_value(release_channel.as_deref());
|
||||
let updater = build_updater(&app_handle, channel)?;
|
||||
let update = updater
|
||||
.check()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to check for updates: {e}"))?;
|
||||
|
||||
Ok(update.map(to_update_metadata))
|
||||
}
|
||||
|
||||
pub async fn download_and_install_app_update<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
release_channel: Option<String>,
|
||||
expected_version: String,
|
||||
on_event: Channel<AppUpdateDownloadEvent>,
|
||||
fn ensure_expected_update_version(
|
||||
update: &tauri_plugin_updater::Update,
|
||||
expected_version: &str,
|
||||
) -> Result<(), String> {
|
||||
let channel = ReleaseChannel::from_settings_value(release_channel.as_deref());
|
||||
let updater = build_updater(&app_handle, channel)?;
|
||||
let update = updater
|
||||
.check()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to refresh update metadata: {e}"))?
|
||||
.ok_or_else(|| "No update is currently available".to_string())?;
|
||||
|
||||
if update.version != expected_version {
|
||||
return Err(format!(
|
||||
"Expected update version {}, found {}",
|
||||
expected_version, update.version
|
||||
));
|
||||
if update.version == expected_version {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(format!(
|
||||
"Expected update version {}, found {}",
|
||||
expected_version, update.version
|
||||
))
|
||||
}
|
||||
|
||||
async fn install_update(
|
||||
update: tauri_plugin_updater::Update,
|
||||
on_event: Channel<AppUpdateDownloadEvent>,
|
||||
) -> Result<(), String> {
|
||||
let mut started = false;
|
||||
update
|
||||
.download_and_install(
|
||||
@@ -129,9 +220,45 @@ pub async fn download_and_install_app_update<R: Runtime>(
|
||||
.map_err(|e| format!("Failed to download and install update: {e}"))
|
||||
}
|
||||
|
||||
pub async fn check_for_app_update<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
release_channel: Option<String>,
|
||||
) -> Result<Option<AppUpdateMetadata>, String> {
|
||||
let channel = ReleaseChannel::from_settings_value(release_channel.as_deref());
|
||||
let updater = build_updater(&app_handle, updater_endpoint(channel).await?)?;
|
||||
let update = updater
|
||||
.check()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to check for updates: {e}"))?;
|
||||
|
||||
Ok(update.map(to_update_metadata))
|
||||
}
|
||||
|
||||
pub async fn download_and_install_app_update<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
release_channel: Option<String>,
|
||||
expected_version: String,
|
||||
on_event: Channel<AppUpdateDownloadEvent>,
|
||||
) -> Result<(), String> {
|
||||
let channel = ReleaseChannel::from_settings_value(release_channel.as_deref());
|
||||
let updater = build_updater(&app_handle, updater_endpoint(channel).await?)?;
|
||||
let update = updater
|
||||
.check()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to refresh update metadata: {e}"))?
|
||||
.ok_or_else(|| "No update is currently available".to_string())?;
|
||||
|
||||
ensure_expected_update_version(&update, &expected_version)?;
|
||||
|
||||
install_update(update, on_event).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{AppUpdateDownloadEvent, AppUpdateMetadata, ReleaseChannel};
|
||||
use super::{
|
||||
latest_alpha_release_metadata_url, AppUpdateDownloadEvent, AppUpdateMetadata, GitHubAsset,
|
||||
GitHubRelease, ReleaseChannel,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
@@ -178,6 +305,64 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alpha_release_metadata_url_uses_highest_calendar_sequence_tag() {
|
||||
let releases = vec![
|
||||
github_alpha_release(
|
||||
"alpha-v2026.5.8-alpha.0007",
|
||||
"https://github.com/refactoringhq/tolaria/releases/download/alpha-v2026.5.8-alpha.0007/alpha-latest.json",
|
||||
),
|
||||
github_alpha_release(
|
||||
"alpha-v2026.5.8-alpha.0017",
|
||||
"https://github.com/refactoringhq/tolaria/releases/download/alpha-v2026.5.8-alpha.0017/alpha-latest.json",
|
||||
),
|
||||
github_alpha_release(
|
||||
"alpha-v2026.5.7-alpha.0099",
|
||||
"https://github.com/refactoringhq/tolaria/releases/download/alpha-v2026.5.7-alpha.0099/alpha-latest.json",
|
||||
),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
latest_alpha_release_metadata_url(&releases)
|
||||
.unwrap()
|
||||
.as_str(),
|
||||
"https://github.com/refactoringhq/tolaria/releases/download/alpha-v2026.5.8-alpha.0017/alpha-latest.json"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alpha_release_metadata_url_ignores_drafts_and_non_alpha_assets() {
|
||||
let releases = vec![
|
||||
GitHubRelease {
|
||||
tag_name: "alpha-v2026.5.8-alpha.0018".into(),
|
||||
draft: true,
|
||||
assets: vec![GitHubAsset {
|
||||
name: "alpha-latest.json".into(),
|
||||
browser_download_url: "https://example.com/draft.json".into(),
|
||||
}],
|
||||
},
|
||||
GitHubRelease {
|
||||
tag_name: "stable-v2026.5.8".into(),
|
||||
draft: false,
|
||||
assets: vec![GitHubAsset {
|
||||
name: "stable-latest.json".into(),
|
||||
browser_download_url: "https://example.com/stable.json".into(),
|
||||
}],
|
||||
},
|
||||
github_alpha_release(
|
||||
"alpha-v2026.5.8-alpha.0017",
|
||||
"https://example.com/alpha-latest.json",
|
||||
),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
latest_alpha_release_metadata_url(&releases)
|
||||
.unwrap()
|
||||
.as_str(),
|
||||
"https://example.com/alpha-latest.json"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_metadata_serializes_for_frontend_consumers() {
|
||||
let metadata = AppUpdateMetadata {
|
||||
@@ -227,4 +412,15 @@ mod tests {
|
||||
assert_eq!(serde_json::to_value(event).unwrap(), expected);
|
||||
}
|
||||
}
|
||||
|
||||
fn github_alpha_release(tag_name: &str, browser_download_url: &str) -> GitHubRelease {
|
||||
GitHubRelease {
|
||||
tag_name: tag_name.into(),
|
||||
draft: false,
|
||||
assets: vec![GitHubAsset {
|
||||
name: "alpha-latest.json".into(),
|
||||
browser_download_url: browser_download_url.into(),
|
||||
}],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -938,6 +938,7 @@ export default defineConfig({
|
||||
environment: 'jsdom',
|
||||
setupFiles: ['./src/test/setup.ts'],
|
||||
include: ['src/**/*.{test,spec}.{ts,tsx}'],
|
||||
testTimeout: 10_000,
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'json', 'html', 'lcov'],
|
||||
|
||||
Reference in New Issue
Block a user