//! Persists two independent per-package facts as plain files: the last //! published version, and any version currently pending human review. //! The only module that touches the state directory (see `paths.rs`) on disk. use anyhow::Result; use std::path::Path; /// Last-known-published version per package, so re-runs don't re-flag a /// version already handled. Deliberately just one file per package for /// now — this is where a real review-queue persistence layer plugs in /// later (see docs/SPEC.md > Architecture > Reviewer queue). pub fn load_last_version(state_dir: &Path, name: &str) -> Option { std::fs::read_to_string(state_dir.join(format!("{name}.version"))) .ok() .map(|s| s.trim().to_string()) } pub fn save_last_version(state_dir: &Path, name: &str, version: &str) -> Result<()> { std::fs::create_dir_all(state_dir)?; std::fs::write(state_dir.join(format!("{name}.version")), version)?; Ok(()) } /// Tag currently awaiting human review for a tier 4-6 package (see /// docs/SPEC.md > Architecture > Reviewer queue), if any. Separate from /// `load_last_version`/`save_last_version`: approving a review doesn't /// mean future versions auto-publish, so the two must be tracked /// independently. pub fn load_pending_version(state_dir: &Path, name: &str) -> Option { std::fs::read_to_string(state_dir.join(format!("{name}.pending"))) .ok() .map(|s| s.trim().to_string()) } pub fn save_pending_version(state_dir: &Path, name: &str, version: &str) -> Result<()> { std::fs::create_dir_all(state_dir)?; std::fs::write(state_dir.join(format!("{name}.pending")), version)?; Ok(()) } /// Clears a pending review, e.g. once it's been approved and published. /// Not an error if there was nothing pending. pub fn clear_pending_version(state_dir: &Path, name: &str) -> Result<()> { match std::fs::remove_file(state_dir.join(format!("{name}.pending"))) { Ok(()) => Ok(()), Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()), Err(e) => Err(e.into()), } } #[cfg(test)] mod tests { use super::*; #[test] fn load_last_version_missing_file_returns_none() { let dir = tempfile::tempdir().unwrap(); assert_eq!(load_last_version(dir.path(), "nonexistent"), None); } #[test] fn save_then_load_roundtrips() { let dir = tempfile::tempdir().unwrap(); save_last_version(dir.path(), "uv", "0.12.15").unwrap(); assert_eq!( load_last_version(dir.path(), "uv"), Some("0.12.15".to_string()) ); } #[test] fn load_last_version_trims_trailing_whitespace() { let dir = tempfile::tempdir().unwrap(); std::fs::write(dir.path().join("uv.version"), "0.12.15\n").unwrap(); assert_eq!( load_last_version(dir.path(), "uv"), Some("0.12.15".to_string()) ); } #[test] fn save_last_version_creates_state_dir_if_missing() { let dir = tempfile::tempdir().unwrap(); let nested = dir.path().join("nested/state"); save_last_version(&nested, "uv", "0.12.15").unwrap(); assert_eq!( load_last_version(&nested, "uv"), Some("0.12.15".to_string()) ); } #[test] fn load_pending_version_missing_file_returns_none() { let dir = tempfile::tempdir().unwrap(); assert_eq!(load_pending_version(dir.path(), "scaleway-cli"), None); } #[test] fn save_then_load_pending_roundtrips() { let dir = tempfile::tempdir().unwrap(); save_pending_version(dir.path(), "scaleway-cli", "v2.62.0").unwrap(); assert_eq!( load_pending_version(dir.path(), "scaleway-cli"), Some("v2.62.0".to_string()) ); } #[test] fn clear_pending_version_removes_it() { let dir = tempfile::tempdir().unwrap(); save_pending_version(dir.path(), "scaleway-cli", "v2.62.0").unwrap(); clear_pending_version(dir.path(), "scaleway-cli").unwrap(); assert_eq!(load_pending_version(dir.path(), "scaleway-cli"), None); } #[test] fn clear_pending_version_is_a_noop_when_nothing_pending() { let dir = tempfile::tempdir().unwrap(); assert!(clear_pending_version(dir.path(), "scaleway-cli").is_ok()); } #[test] fn pending_and_last_version_are_tracked_independently() { let dir = tempfile::tempdir().unwrap(); save_last_version(dir.path(), "scaleway-cli", "v2.61.0").unwrap(); save_pending_version(dir.path(), "scaleway-cli", "v2.62.0").unwrap(); assert_eq!( load_last_version(dir.path(), "scaleway-cli"), Some("v2.61.0".to_string()) ); assert_eq!( load_pending_version(dir.path(), "scaleway-cli"), Some("v2.62.0".to_string()) ); } }