diff --git a/CLAUDE.md b/CLAUDE.md index 62684de65..8db671d79 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,7 +15,7 @@ Narrative architecture: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md). Frontend | **`Cargo.toml`** (root) | Core crate; `cargo build --bin openhuman` produces the sidecar staged by `app`'s `core:stage` | | **`docs/`** | Architecture and module guides | -Commands assume the **repo root**; `yarn dev` delegates to the `app` workspace. +Commands assume the **repo root**; `pnpm dev` delegates to the `app` workspace. (Repo migrated from yarn to pnpm — `package.json` enforces pnpm via the `packageManager` field.) --- @@ -34,14 +34,14 @@ Commands assume the **repo root**; `yarn dev` delegates to the `app` workspace. ## Commands (from repo root) ```bash -yarn dev # Frontend + Tauri dev -yarn tauri dev # Desktop with Tauri (loads env via scripts/load-dotenv.sh) -yarn build # Production UI build -yarn typecheck # Typecheck (app workspace) -yarn lint # ESLint -yarn format # Prettier write -yarn format:check # Prettier check -cd app && yarn core:stage # Stage openhuman binary next to Tauri resources +pnpm dev # Frontend + Tauri dev +pnpm tauri dev # Desktop with Tauri (loads env via scripts/load-dotenv.sh) +pnpm build # Production UI build +pnpm typecheck # Typecheck (app workspace) +pnpm lint # ESLint +pnpm format # Prettier write +pnpm format:check # Prettier check +cd app && pnpm core:stage # Stage openhuman binary next to Tauri resources # Rust — core library + CLI cargo check --manifest-path Cargo.toml @@ -51,7 +51,7 @@ cargo build --manifest-path Cargo.toml --bin openhuman cargo check --manifest-path app/src-tauri/Cargo.toml ``` -**Tests**: Vitest in `app/` (`yarn test:unit`, `yarn test:coverage`); Rust via `cargo test`. +**Tests**: Vitest in `app/` (`pnpm test:unit`, `pnpm test:coverage`); Rust via `cargo test`. **Quality**: ESLint + Prettier + Husky in `app`. --- @@ -73,7 +73,7 @@ cargo check --manifest-path app/src-tauri/Cargo.toml - Co-locate as `*.test.ts` / `*.test.tsx` under `app/src/**`. - Config: `app/test/vitest.config.ts`; setup: `app/src/test/setup.ts`. -- Run: `yarn test:unit`, `yarn test:coverage`. +- Run: `pnpm test:unit`, `pnpm test:coverage`. - Prefer behavior over implementation. Use helpers in `app/src/test/`. No real network, no time flakes. ### Shared mock backend @@ -81,7 +81,7 @@ cargo check --manifest-path app/src-tauri/Cargo.toml Used by both unit and Rust tests. - Core: `scripts/mock-api-core.mjs` · server: `scripts/mock-api-server.mjs` · E2E wrapper: `app/test/e2e/mock-server.ts`. - Admin: `GET /__admin/health`, `POST /__admin/reset`, `POST /__admin/behavior`, `GET /__admin/requests`. -- Run manually: `yarn mock:api`. +- Run manually: `pnpm mock:api`. ### E2E (WDIO — dual platform) @@ -91,9 +91,9 @@ Full guide: [`docs/E2E-TESTING.md`](docs/E2E-TESTING.md). - Specs: `app/test/e2e/specs/*.spec.ts`. Helpers in `app/test/e2e/helpers/`. Config: `app/test/wdio.conf.ts`. ```bash -yarn test:e2e:build +pnpm test:e2e:build bash app/scripts/e2e-run-spec.sh test/e2e/specs/smoke.spec.ts smoke -yarn test:e2e:all:flows +pnpm test:e2e:all:flows docker compose -f e2e/docker-compose.yml run --rm e2e # Linux E2E on macOS ``` @@ -106,7 +106,7 @@ Use `element-helpers.ts` (`clickNativeButton`, `waitForWebView`, `clickToggle`) ### Rust tests with mock ```bash -yarn test:rust +pnpm test:rust bash scripts/test-rust-with-mock.sh --test json_rpc_e2e ``` @@ -262,7 +262,7 @@ Specify → prove in Rust → prove over RPC → surface in the UI → test. ## Platform notes -- **Vendored CEF-aware `tauri-cli`**: runtime is CEF; only the vendored CLI at `app/src-tauri/vendor/tauri-cef/crates/tauri-cli` bundles Chromium into `Contents/Frameworks/`. Stock `@tauri-apps/cli` produces a broken bundle (panic in `cef::library_loader::LibraryLoader::new`). `yarn dev:app` and all `cargo tauri` scripts call `yarn tauri:ensure` which runs [`scripts/ensure-tauri-cli.sh`](scripts/ensure-tauri-cli.sh). If overwritten, reinstall with `cargo install --locked --path app/src-tauri/vendor/tauri-cef/crates/tauri-cli`. +- **Vendored CEF-aware `tauri-cli`**: runtime is CEF; only the vendored CLI at `app/src-tauri/vendor/tauri-cef/crates/tauri-cli` bundles Chromium into `Contents/Frameworks/`. Stock `@tauri-apps/cli` produces a broken bundle (panic in `cef::library_loader::LibraryLoader::new`). `pnpm dev:app` and all `cargo tauri` scripts call `pnpm tauri:ensure` which runs [`scripts/ensure-tauri-cli.sh`](scripts/ensure-tauri-cli.sh). If overwritten, reinstall with `cargo install --locked --path app/src-tauri/vendor/tauri-cef/crates/tauri-cli`. - **macOS deep links**: often require a built `.app` bundle, not just `tauri dev`. - **Tauri environment guard**: use `isTauri()` (from `app/src/services/webviewAccountService.ts`) or wrap `invoke(...)` in `try/catch`; do not check `window.__TAURI__` directly — it is not present at module load and bypasses the established wrapper contract. - **Core sidecar**: must be staged so `core_rpc` can reach the `openhuman` binary (see `scripts/stage-core-sidecar.mjs`). diff --git a/Cargo.lock b/Cargo.lock index d2a3bac32..f78bac83a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4420,7 +4420,7 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openhuman" -version = "0.52.28" +version = "0.52.20" dependencies = [ "aes-gcm", "anyhow", diff --git a/app/src-tauri/Cargo.lock b/app/src-tauri/Cargo.lock index 90824953b..7b9573dfa 100644 --- a/app/src-tauri/Cargo.lock +++ b/app/src-tauri/Cargo.lock @@ -12,6 +12,7 @@ dependencies = [ "cef", "chrono", "env_logger", + "flate2", "futures-util", "log", "mac-notification-sys", @@ -25,6 +26,7 @@ dependencies = [ "semver", "serde", "serde_json", + "tar", "tauri", "tauri-build", "tauri-plugin-deep-link", @@ -34,6 +36,7 @@ dependencies = [ "tauri-runtime-cef", "tokio", "tokio-tungstenite", + "zip", ] [[package]] @@ -116,6 +119,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -892,6 +904,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -6574,12 +6597,41 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap 2.13.0", + "memchr", + "thiserror 2.0.18", + "zopfli", +] + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + [[package]] name = "zvariant" version = "5.10.0" diff --git a/app/src-tauri/Cargo.toml b/app/src-tauri/Cargo.toml index dff5effcb..b5490450a 100644 --- a/app/src-tauri/Cargo.toml +++ b/app/src-tauri/Cargo.toml @@ -66,6 +66,13 @@ futures-util = { version = "0.3", default-features = false, features = ["sink", reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } semver = "1" +# Auto-update archive extraction. GitHub release assets ship as +# `openhuman-core--.tar.gz` on Unix and `….zip` +# on Windows; the inner binary is `openhuman-core` (or `.exe`) with no +# wrapping directory. See `core_update.rs::extract_archive`. +flate2 = "1" +tar = "0.4" +zip = { version = "2", default-features = false, features = ["deflate"] } log = "0.4" env_logger = "0.11" diff --git a/app/src-tauri/src/core_update.rs b/app/src-tauri/src/core_update.rs index 006ce2fd2..59e4cdb43 100644 --- a/app/src-tauri/src/core_update.rs +++ b/app/src-tauri/src/core_update.rs @@ -6,7 +6,7 @@ //! process, and restarts with the new binary. use std::io::Write; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; @@ -160,13 +160,53 @@ fn platform_triple() -> &'static str { } /// Find the right asset for this platform. +/// +/// Current release format (since 0.52.x): `openhuman-core--.tar.gz` +/// on Unix, `.zip` on Windows. The archive contains a single `openhuman-core` +/// (or `openhuman-core.exe`) file with no wrapping directory. +/// +/// Legacy format (kept as fallback for older releases): a raw binary named +/// `openhuman-core-` (or `.exe`). fn find_platform_asset(assets: &[GitHubAsset]) -> Option<&GitHubAsset> { let triple = platform_triple(); - let expected = format!("openhuman-core-{triple}"); + let archive_ext = if cfg!(windows) { ".zip" } else { ".tar.gz" }; + + // New versioned-archive format: `openhuman-core-0.52.26-aarch64-apple-darwin.tar.gz` + let archive_match = assets.iter().find(|a| { + a.name.starts_with("openhuman-core-") + && a.name.contains(triple) + && a.name.ends_with(archive_ext) + // Defensive: avoid matching detached signatures or checksums that + // happen to share the prefix (e.g. `…tar.gz.sha256`, `…tar.gz.sig`). + && !a.name.ends_with(".sha256") + && !a.name.ends_with(".sig") + }); + if archive_match.is_some() { + return archive_match; + } + + // Legacy raw-binary format. + let legacy = format!("openhuman-core-{triple}"); + let legacy_exe = format!("{legacy}.exe"); assets .iter() - .find(|a| a.name == expected || a.name == format!("{expected}.exe")) - .or_else(|| assets.iter().find(|a| a.name.starts_with(&expected))) + .find(|a| a.name == legacy || a.name == legacy_exe) +} + +/// Filename the staged core binary must be saved as for `core_process::default_core_bin` +/// to discover it on subsequent runs. +fn staged_binary_name() -> String { + let triple = platform_triple(); + if cfg!(windows) { + format!("openhuman-core-{triple}.exe") + } else { + format!("openhuman-core-{triple}") + } +} + +/// True if the asset name looks like an archive we need to extract (vs. a raw binary). +fn is_archive_asset(name: &str) -> bool { + name.ends_with(".tar.gz") || name.ends_with(".tgz") || name.ends_with(".zip") } /// Fetch the latest release from GitHub. @@ -195,11 +235,106 @@ async fn fetch_latest_release() -> Result { .map_err(|e| format!("failed to parse release: {e}")) } -/// Download a binary from `url` and stage it at `dest`. +/// Build a unique sibling temp path next to `dest` to stage writes before an atomic rename. +fn unique_tmp_path(dest: &Path) -> PathBuf { + let tmp_name = format!( + ".openhuman-update-{}.tmp", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_millis()) + .unwrap_or(0) + ); + dest.parent() + .unwrap_or_else(|| std::path::Path::new(".")) + .join(tmp_name) +} + +/// Make a file executable (Unix) and rename it atomically to `dest`. On rename +/// failure the temp file is best-effort cleaned up. +fn finalize_executable(tmp: &Path, dest: &Path) -> Result<(), String> { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(tmp, std::fs::Permissions::from_mode(0o755)) + .map_err(|e| format!("set permissions: {e}"))?; + } + std::fs::rename(tmp, dest).map_err(|e| { + let _ = std::fs::remove_file(tmp); + format!("rename staged binary: {e}") + }) +} + +/// Extract the inner core binary from a downloaded archive into `dest`. /// -/// Uses a unique temp file (UUID-based) to avoid conflicts from concurrent downloads. -/// Sets executable permissions on Unix before the atomic rename. -async fn download_binary(url: &str, dest: &PathBuf) -> Result<(), String> { +/// The archive is expected to contain a single file named `openhuman-core` +/// (or `openhuman-core.exe`) at the root — matching the layout produced by +/// the release workflow. `dest` must be the final binary path (with the +/// platform-triple-suffixed name `default_core_bin` looks for). +fn extract_archive(archive_path: &Path, dest: &Path) -> Result<(), String> { + let inner_name = if cfg!(windows) { + "openhuman-core.exe" + } else { + "openhuman-core" + }; + + let archive_name = archive_path + .file_name() + .and_then(|s| s.to_str()) + .unwrap_or(""); + + let file = std::fs::File::open(archive_path).map_err(|e| format!("open archive: {e}"))?; + + if archive_name.ends_with(".zip") { + let mut zip = zip::ZipArchive::new(file).map_err(|e| format!("read zip: {e}"))?; + let mut entry = zip + .by_name(inner_name) + .map_err(|e| format!("zip entry '{inner_name}' missing: {e}"))?; + let tmp = unique_tmp_path(dest); + { + let mut out = std::fs::File::create(&tmp).map_err(|e| format!("create temp: {e}"))?; + std::io::copy(&mut entry, &mut out).map_err(|e| format!("extract zip: {e}"))?; + out.flush().map_err(|e| format!("flush extracted: {e}"))?; + } + finalize_executable(&tmp, dest)?; + return Ok(()); + } + + // Default: tar.gz / tgz + let gz = flate2::read::GzDecoder::new(file); + let mut tar = tar::Archive::new(gz); + let entries = tar + .entries() + .map_err(|e| format!("read tar entries: {e}"))?; + for entry in entries { + let mut entry = entry.map_err(|e| format!("tar entry: {e}"))?; + let entry_path = entry.path().map_err(|e| format!("entry path: {e}"))?; + let matches = entry_path + .file_name() + .and_then(|s| s.to_str()) + .map(|n| n == inner_name) + .unwrap_or(false); + if !matches { + continue; + } + let tmp = unique_tmp_path(dest); + { + let mut out = std::fs::File::create(&tmp).map_err(|e| format!("create temp: {e}"))?; + std::io::copy(&mut entry, &mut out).map_err(|e| format!("extract tar: {e}"))?; + out.flush().map_err(|e| format!("flush extracted: {e}"))?; + } + finalize_executable(&tmp, dest)?; + return Ok(()); + } + + Err(format!( + "archive {} contained no entry named '{inner_name}'", + archive_path.display() + )) +} + +/// Download `url` to `dest` atomically. Used for both raw binaries (legacy +/// release format) and archive files (current format — caller then extracts). +async fn download_to_file(url: &str, dest: &Path) -> Result<(), String> { let client = reqwest::Client::builder() .user_agent("openhuman-tauri-updater") .timeout(std::time::Duration::from_secs(300)) @@ -227,19 +362,7 @@ async fn download_binary(url: &str, dest: &PathBuf) -> Result<(), String> { dest.display() ); - // Use a unique temp filename to avoid collisions from concurrent writes. - let tmp_name = format!( - ".openhuman-update-{}.tmp", - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0) - ); - let tmp = dest - .parent() - .unwrap_or_else(|| std::path::Path::new(".")) - .join(tmp_name); - + let tmp = unique_tmp_path(dest); { let mut file = std::fs::File::create(&tmp).map_err(|e| format!("create temp file: {e}"))?; file.write_all(&bytes) @@ -247,17 +370,11 @@ async fn download_binary(url: &str, dest: &PathBuf) -> Result<(), String> { file.flush().map_err(|e| format!("flush temp file: {e}"))?; } - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - std::fs::set_permissions(&tmp, std::fs::Permissions::from_mode(0o755)) - .map_err(|e| format!("set permissions: {e}"))?; - } - + // Move into place. Caller is responsible for marking executable when the + // payload is a raw binary; archives stay non-executable on disk. std::fs::rename(&tmp, dest).map_err(|e| { - // Best-effort cleanup of temp file on rename failure. let _ = std::fs::remove_file(&tmp); - format!("rename staged binary: {e}") + format!("rename downloaded file: {e}") })?; Ok(()) @@ -385,10 +502,17 @@ UI features (e.g. channel connect) may not match RPC until the core is updated." } } - let dest = staging_dir + // Where the downloaded asset lands (named after the release asset). + let download_dest = staging_dir .as_ref() .map(|d| d.join(&asset.name)) .unwrap_or_else(|| PathBuf::from(&asset.name)); + // Final binary path that `default_core_bin` will pick up next launch. + let binary_dest = staging_dir + .as_ref() + .map(|d| d.join(staged_binary_name())) + .unwrap_or_else(|| PathBuf::from(staged_binary_name())); + let asset_is_archive = is_archive_asset(&asset.name); // Step 4: Acquire restart lock, shutdown old process, download, stage, restart. // Hold the lock across download + staging + restart to prevent concurrent updates. @@ -409,12 +533,48 @@ UI features (e.g. channel connect) may not match RPC until the core is updated." waited += 50; } - // Download and stage the new binary. - download_binary(&asset.browser_download_url, &dest).await?; - log::info!("[core-update] staged new binary at {}", dest.display()); + // Download the asset. + download_to_file(&asset.browser_download_url, &download_dest).await?; + log::info!( + "[core-update] downloaded asset to {}", + download_dest.display() + ); + + if asset_is_archive { + // Extract the inner `openhuman-core` binary into the staged location. + extract_archive(&download_dest, &binary_dest)?; + log::info!( + "[core-update] extracted core binary to {}", + binary_dest.display() + ); + // Best-effort cleanup of the archive (don't fail the update if this fails). + if let Err(e) = std::fs::remove_file(&download_dest) { + log::warn!( + "[core-update] could not remove archive {}: {e}", + download_dest.display() + ); + } + } else { + // Legacy raw-binary asset: rename into the canonical staged path + // and mark executable. + if download_dest != binary_dest { + std::fs::rename(&download_dest, &binary_dest) + .map_err(|e| format!("rename legacy binary: {e}"))?; + } + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(&binary_dest, std::fs::Permissions::from_mode(0o755)) + .map_err(|e| format!("set permissions on staged binary: {e}"))?; + } + log::info!( + "[core-update] staged legacy raw binary at {}", + binary_dest.display() + ); + } // Point the handle at the new binary so ensure_running launches it. - handle.set_core_bin(dest).await; + handle.set_core_bin(binary_dest).await; emit_event(&app, "core-update:status", "restarting"); @@ -485,6 +645,119 @@ fn emit_event(app: &Option>, event: &str, pa mod tests { use super::*; + fn asset(name: &str) -> GitHubAsset { + GitHubAsset { + name: name.to_string(), + browser_download_url: format!("https://example.test/{name}"), + } + } + + #[test] + fn find_platform_asset_matches_versioned_archive() { + let triple = platform_triple(); + let archive_ext = if cfg!(windows) { "zip" } else { "tar.gz" }; + let archive_name = format!("openhuman-core-0.52.26-{triple}.{archive_ext}"); + let assets = vec![ + asset("latest.json"), + asset(&format!("{archive_name}.sha256")), + asset(&archive_name), + asset(&format!("{archive_name}.sig")), + asset(&format!("OpenHuman_0.52.26_{triple}.app.tar.gz")), + ]; + let m = find_platform_asset(&assets).expect("should match versioned archive"); + assert_eq!(m.name, archive_name); + } + + #[test] + fn find_platform_asset_falls_back_to_legacy_raw_binary() { + let triple = platform_triple(); + let legacy_name = if cfg!(windows) { + format!("openhuman-core-{triple}.exe") + } else { + format!("openhuman-core-{triple}") + }; + let assets = vec![asset("latest.json"), asset(&legacy_name)]; + let m = find_platform_asset(&assets).expect("legacy raw binary should match"); + assert_eq!(m.name, legacy_name); + } + + #[test] + fn find_platform_asset_skips_signatures_and_checksums() { + let triple = platform_triple(); + let archive_ext = if cfg!(windows) { "zip" } else { "tar.gz" }; + let assets = vec![ + asset(&format!( + "openhuman-core-0.52.26-{triple}.{archive_ext}.sha256" + )), + asset(&format!( + "openhuman-core-0.52.26-{triple}.{archive_ext}.sig" + )), + ]; + assert!(find_platform_asset(&assets).is_none()); + } + + #[test] + fn is_archive_asset_recognises_known_extensions() { + assert!(is_archive_asset( + "openhuman-core-0.52.26-aarch64-apple-darwin.tar.gz" + )); + assert!(is_archive_asset( + "openhuman-core-0.52.26-x86_64-pc-windows-msvc.zip" + )); + assert!(is_archive_asset("foo.tgz")); + assert!(!is_archive_asset("openhuman-core-aarch64-apple-darwin")); + assert!(!is_archive_asset("openhuman-core.exe")); + } + + #[test] + fn extract_archive_pulls_inner_binary_from_targz() { + use std::io::Read; + + let dir = std::env::temp_dir().join(format!( + "oh-extract-test-{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + std::fs::create_dir_all(&dir).unwrap(); + + // Build an in-memory tar.gz with a single `openhuman-core` (or .exe) entry. + let inner = if cfg!(windows) { + "openhuman-core.exe" + } else { + "openhuman-core" + }; + let payload = b"#!fake-binary-bytes"; + + let archive_path = dir.join("test.tar.gz"); + { + let tar_gz = std::fs::File::create(&archive_path).unwrap(); + let enc = flate2::write::GzEncoder::new(tar_gz, flate2::Compression::default()); + let mut builder = tar::Builder::new(enc); + let mut header = tar::Header::new_gnu(); + header.set_size(payload.len() as u64); + header.set_mode(0o755); + header.set_cksum(); + builder + .append_data(&mut header, inner, &payload[..]) + .unwrap(); + builder.into_inner().unwrap().finish().unwrap(); + } + + let dest = dir.join("openhuman-core-staged"); + extract_archive(&archive_path, &dest).expect("extract should succeed"); + + let mut got = Vec::new(); + std::fs::File::open(&dest) + .unwrap() + .read_to_end(&mut got) + .unwrap(); + assert_eq!(got, payload); + + let _ = std::fs::remove_dir_all(&dir); + } + #[test] fn outdated_detection() { assert!(is_outdated("0.49.17", "0.51.8")); diff --git a/app/src-tauri/tauri.conf.json b/app/src-tauri/tauri.conf.json index b05cfaf84..2a5e3ca9a 100644 --- a/app/src-tauri/tauri.conf.json +++ b/app/src-tauri/tauri.conf.json @@ -75,7 +75,7 @@ "plugins": { "updater": { "active": false, - "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDc0OTREMjkxREFCNUIzRTEKUldUaHM3WGFrZEtVZEJzZWtMTlc5dGxnT0R2Q3hUTWVaclJWSm9JUFpPcVFUV2RBSG5oNFN6UjQK", + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDdDNDNENzk3QzVCN0MwNDcKUldSSHdMZkZsOWREZkpicit5c2ZWTDJtcDVhT25WMXkzbUJqcW5ucVBDdU1kN29GbUwwRDFkTkoK", "endpoints": [ "https://github.com/tinyhumansai/openhuman/releases/latest/download/latest.json" ]