mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Persist update dismissal without cache (#27783)
## Summary Choosing “Don’t remind me” can silently fail when `version.json` disappears before dismissal because `dismiss_version` returns success without writing anything. The same update can then reappear on the next launch. Initialize a minimal `VersionInfo` from the selected version when the cache cannot be read, then persist the dismissal through the existing write path. Fixes #27147
This commit is contained in:
committed by
GitHub
Unverified
parent
c375deaf66
commit
096e5e76a2
@@ -207,6 +207,8 @@ mod update_prompt;
|
||||
#[cfg(any(not(debug_assertions), test))]
|
||||
mod update_versions;
|
||||
mod updates;
|
||||
#[cfg(any(not(debug_assertions), test))]
|
||||
mod updates_cache;
|
||||
mod version;
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
mod voice;
|
||||
|
||||
@@ -8,17 +8,19 @@ use crate::update_action::UpdateAction;
|
||||
use crate::update_versions::extract_version_from_latest_tag;
|
||||
use crate::update_versions::is_newer;
|
||||
use crate::update_versions::is_source_build_version;
|
||||
use chrono::DateTime;
|
||||
use crate::updates_cache::VersionInfo;
|
||||
use crate::updates_cache::read_version_info;
|
||||
use crate::updates_cache::version_filepath;
|
||||
use chrono::Duration;
|
||||
use chrono::Utc;
|
||||
use codex_login::default_client::create_client;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::version::CODEX_CLI_VERSION;
|
||||
|
||||
pub(crate) use crate::updates_cache::dismiss_version;
|
||||
|
||||
pub fn get_upgrade_version(config: &Config) -> Option<String> {
|
||||
if !config.check_for_update_on_startup || is_source_build_version(CODEX_CLI_VERSION) {
|
||||
return None;
|
||||
@@ -51,16 +53,6 @@ pub fn get_upgrade_version(config: &Config) -> Option<String> {
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct VersionInfo {
|
||||
latest_version: String,
|
||||
// ISO-8601 timestamp (RFC3339)
|
||||
last_checked_at: DateTime<Utc>,
|
||||
#[serde(default)]
|
||||
dismissed_version: Option<String>,
|
||||
}
|
||||
|
||||
const VERSION_FILENAME: &str = "version.json";
|
||||
// We use the latest version from the cask if installation is via homebrew - homebrew does not immediately pick up the latest release and can lag behind.
|
||||
const HOMEBREW_CASK_API_URL: &str = "https://formulae.brew.sh/api/cask/codex.json";
|
||||
const LATEST_RELEASE_URL: &str = "https://api.github.com/repos/openai/codex/releases/latest";
|
||||
@@ -75,15 +67,6 @@ struct HomebrewCaskInfo {
|
||||
version: String,
|
||||
}
|
||||
|
||||
fn version_filepath(config: &Config) -> PathBuf {
|
||||
config.codex_home.join(VERSION_FILENAME).into_path_buf()
|
||||
}
|
||||
|
||||
fn read_version_info(version_file: &Path) -> anyhow::Result<VersionInfo> {
|
||||
let contents = std::fs::read_to_string(version_file)?;
|
||||
Ok(serde_json::from_str(&contents)?)
|
||||
}
|
||||
|
||||
async fn check_for_update(version_file: &Path, action: Option<UpdateAction>) -> anyhow::Result<()> {
|
||||
let latest_version = match action {
|
||||
Some(UpdateAction::BrewUpgrade) => {
|
||||
@@ -159,20 +142,3 @@ pub fn get_upgrade_version_for_popup(config: &Config) -> Option<String> {
|
||||
}
|
||||
Some(latest)
|
||||
}
|
||||
|
||||
/// Persist a dismissal for the current latest version so we don't show
|
||||
/// the update popup again for this version.
|
||||
pub async fn dismiss_version(config: &Config, version: &str) -> anyhow::Result<()> {
|
||||
let version_file = version_filepath(config);
|
||||
let mut info = match read_version_info(&version_file) {
|
||||
Ok(info) => info,
|
||||
Err(_) => return Ok(()),
|
||||
};
|
||||
info.dismissed_version = Some(version.to_string());
|
||||
let json_line = format!("{}\n", serde_json::to_string(&info)?);
|
||||
if let Some(parent) = version_file.parent() {
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
}
|
||||
tokio::fs::write(version_file, json_line).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
use crate::legacy_core::config::Config;
|
||||
use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub(crate) struct VersionInfo {
|
||||
pub(crate) latest_version: String,
|
||||
// ISO-8601 timestamp (RFC3339)
|
||||
pub(crate) last_checked_at: DateTime<Utc>,
|
||||
#[serde(default)]
|
||||
pub(crate) dismissed_version: Option<String>,
|
||||
}
|
||||
|
||||
const VERSION_FILENAME: &str = "version.json";
|
||||
|
||||
pub(crate) fn version_filepath(config: &Config) -> PathBuf {
|
||||
config.codex_home.join(VERSION_FILENAME).into_path_buf()
|
||||
}
|
||||
|
||||
pub(crate) fn read_version_info(version_file: &Path) -> anyhow::Result<VersionInfo> {
|
||||
let contents = std::fs::read_to_string(version_file)?;
|
||||
Ok(serde_json::from_str(&contents)?)
|
||||
}
|
||||
|
||||
/// Persist a dismissal for the current latest version so we don't show
|
||||
/// the update popup again for this version.
|
||||
pub(crate) async fn dismiss_version(config: &Config, version: &str) -> anyhow::Result<()> {
|
||||
let version_file = version_filepath(config);
|
||||
let mut info = match read_version_info(&version_file) {
|
||||
Ok(info) => info,
|
||||
Err(_) => VersionInfo {
|
||||
latest_version: version.to_string(),
|
||||
last_checked_at: DateTime::<Utc>::UNIX_EPOCH,
|
||||
dismissed_version: None,
|
||||
},
|
||||
};
|
||||
info.dismissed_version = Some(version.to_string());
|
||||
let json_line = format!("{}\n", serde_json::to_string(&info)?);
|
||||
if let Some(parent) = version_file.parent() {
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
}
|
||||
tokio::fs::write(version_file, json_line).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "updates_cache_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,29 @@
|
||||
use super::*;
|
||||
use crate::legacy_core::config::ConfigBuilder;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn dismiss_version_creates_cache_file_when_missing() {
|
||||
let codex_home = tempdir().expect("temp codex home");
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.build()
|
||||
.await
|
||||
.expect("load config");
|
||||
let version_file = version_filepath(&config);
|
||||
|
||||
dismiss_version(&config, "999.0.0")
|
||||
.await
|
||||
.expect("dismiss version");
|
||||
|
||||
let info = read_version_info(&version_file).expect("read version info");
|
||||
assert_eq!(info.last_checked_at, DateTime::<Utc>::UNIX_EPOCH);
|
||||
assert_eq!(
|
||||
(
|
||||
info.latest_version.as_str(),
|
||||
info.dismissed_version.as_deref()
|
||||
),
|
||||
("999.0.0", Some("999.0.0"))
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user