From e44b9082e589ecfb05460ef431c4ed3626d5056b Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Wed, 16 Jul 2025 14:42:01 +0200 Subject: [PATCH 01/28] Integrate full package version history management into PackageRegistry using a Move.package-history.toml file The `Move.package-history.toml` file * should be located in the same directory as the `Move.lock` file * contains all necessary data * can be maintained manually or by using a package-history CLI tool --- product_common/src/package_registry.rs | 280 ++++++++++++++++++++++--- 1 file changed, 247 insertions(+), 33 deletions(-) diff --git a/product_common/src/package_registry.rs b/product_common/src/package_registry.rs index 52d6b28..597d196 100644 --- a/product_common/src/package_registry.rs +++ b/product_common/src/package_registry.rs @@ -34,27 +34,6 @@ impl Env { } } -/// A published package's metadata for a certain environment. -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct Metadata { - pub original_published_id: ObjectID, - pub latest_published_id: ObjectID, - #[serde(deserialize_with = "deserialize_u64_from_str")] - pub published_version: u64, -} - -impl Metadata { - /// Create a new [Metadata] assuming a newly published package. - pub fn from_package_id(package: ObjectID) -> Self { - Self { - original_published_id: package, - latest_published_id: package, - published_version: 1, - } - } -} - fn deserialize_u64_from_str<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, @@ -64,23 +43,26 @@ where String::deserialize(deserializer)?.parse().map_err(D::Error::custom) } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct PackageRegistry { aliases: HashMap, - envs: HashMap, + envs: HashMap>, } impl PackageRegistry { - /// Returns the package [Metadata] for a given `chain`. + /// Returns the historical list of this package's versions for a given `chain`. /// `chain` can either be a chain identifier or its alias. - pub fn metadata(&self, chain: &str) -> Option<&Metadata> { + /// + /// ID at position `0` is the first ever published version of the package, `1` is + /// the second, and so forth until the last, which is the currently active version. + pub fn history(&self, chain: &str) -> Option<&[ObjectID]> { let from_alias = || self.aliases.get(chain).and_then(|chain_id| self.envs.get(chain_id)); - self.envs.get(chain).or_else(from_alias) + self.envs.get(chain).or_else(from_alias).map(|v| v.as_slice()) } /// Returns this package's latest version ID for a given chain. pub fn package_id(&self, chain: &str) -> Option { - self.metadata(chain).map(|meta| meta.latest_published_id) + self.history(chain).and_then(|versions| versions.last()).copied() } /// Returns the alias of a given chain-id. @@ -91,8 +73,13 @@ impl PackageRegistry { .find_map(|(alias, chain)| (chain == chain_id).then_some(alias.as_str())) } + /// Returns the envs of this package registry. + pub fn envs(&self) -> &HashMap> { + &self.envs + } + /// Adds or replaces this package's metadata for a given environment. - pub fn insert_env(&mut self, env: Env, metadata: Metadata) { + pub fn insert_env(&mut self, env: Env, metadata: Vec) { let Env { chain_id, alias } = env; if let Some(alias) = alias { @@ -101,23 +88,69 @@ impl PackageRegistry { self.envs.insert(chain_id, metadata); } + /// Inserts a new package version for a given chain. + pub fn insert_new_package_version(&mut self, chain_id: &str, package: ObjectID) { + let history = self.envs.entry(chain_id.to_string()).or_default(); + if history.last() != Some(&package) { + history.push(package) + } + } + /// Merges another [PackageRegistry] into this one. pub fn join(&mut self, other: PackageRegistry) { self.aliases.extend(other.aliases); self.envs.extend(other.envs); } + /// Creates a [PackageRegistry] from a Move.package-history.toml file. + pub fn from_package_history_toml_str(package_history: &str) -> anyhow::Result { + let mut package_history: toml::Table = package_history.parse()?; + + let ret_val = package_history + .remove("aliases") + .context("invalid Move.package-history.toml file: missing `aliases` table")? + .as_table_mut() + .map(std::mem::take) + .context("invalid Move.package-history.toml file: `aliases` is not a table")? + .into_iter() + .try_fold(Self::default(), |mut registry, (alias, chain_id)| { + let chain_id: String = chain_id + .clone() + .try_into() + .context(format!("invalid Move.package-history.toml file: invalid `chain-id` '{chain_id}' for alias {alias}"))?; + registry.aliases.insert(alias, chain_id); + Ok::(registry) + })?; + + package_history + .remove("envs") + .context("invalid Move.package-history.toml file: missing `envs` table")? + .as_table_mut() + .map(std::mem::take) + .context("invalid Move.package-history.toml file: `envs` is not a table")? + .into_iter() + .try_fold(ret_val, |mut registry, (chain_id, versions)| { + let versions: Vec = versions + .try_into() + .context(format!("invalid Move.package-history.toml file: invalid versions for {chain_id}"))?; + registry.envs.insert(chain_id, versions); + Ok(registry) + }) + } + /// Creates a [PackageRegistry] from a Move.lock file. pub fn from_move_lock_content(move_lock: &str) -> anyhow::Result { let mut move_lock: toml::Table = move_lock.parse()?; - move_lock + let mut move_lock_iter = move_lock .remove("env") .context("invalid Move.lock file: missing `env` table")? .as_table_mut() .map(std::mem::take) .context("invalid Move.lock file: `env` is not a table")? - .into_iter() + .into_iter(); + + move_lock_iter .try_fold(Self::default(), |mut registry, (alias, table)| { let toml::Value::Table(mut table) = table else { anyhow::bail!("invalid Move.lock file: invalid `env` table"); @@ -128,13 +161,194 @@ impl PackageRegistry { .try_into() .context("invalid Move.lock file: invalid `chain-id`")?; + let original_published_id: String = remove_first_and_last_char_from_string( + table.get("original-published-id") + .context(format!("invalid Move.lock file: missing `original-published-id` for env {alias}"))? + .to_string() + ); + let latest_published_id: String = remove_first_and_last_char_from_string( + table.get("latest-published-id") + .context(format!("invalid Move.lock file: missing `latest-published-id` for env {alias}"))? + .to_string() + ); + + let mut metadata = vec![ObjectID::from_hex_literal(original_published_id.as_str())?]; + if original_published_id != latest_published_id { + metadata.push(ObjectID::from_hex_literal(latest_published_id.as_str())?); + } + let env = Env::new_with_alias(chain_id, alias.clone()); - let metadata = table - .try_into() - .context(format!("invalid Move.lock file: invalid env metadata for {alias}"))?; registry.insert_env(env, metadata); Ok(registry) }) } } + +fn remove_first_and_last_char_from_string(value: String) -> String { + let mut chars = value.chars(); + chars.next(); + chars.next_back(); + chars.as_str().to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! object_id { + ($id:literal) => { + ObjectID::from_hex_literal($id).unwrap() + }; + } + + const PACKAGE_HISTORY_TOML: &str = r#" + [aliases] + mainnet = "6364aad5" + testnet = "2304aa97" + devnet = "e678123a" + + [envs] + 6364aad5 = ["0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"] + 2304aa97 = ["0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555", "0x3403da7ec4cd2ff9bdf6f34c0b8df5a2bd62c798089feb0d2ebf1c2e953296dc"] + e678123a = ["0xe6fa03d273131066036f1d2d4c3d919b9abbca93910769f26a924c7a01811103", "0x6a976d3da90db5d27f8a0c13b3268a37e582b455cfc7bf72d6461f6e8f668823"] + "#; + + #[test] + fn deserialize_package_registry_from_valid_toml() { + let registry = PackageRegistry::from_package_history_toml_str(PACKAGE_HISTORY_TOML).unwrap(); + assert_eq!(registry.aliases.get("mainnet"), Some(&"6364aad5".to_string())); + assert_eq!(registry.aliases.get("testnet"), Some(&"2304aa97".to_string())); + assert_eq!(registry.envs.get("6364aad5").unwrap().len(), 1); + assert_eq!(registry.envs.get("2304aa97").unwrap().len(), 2); + assert_eq!(registry.history("mainnet").unwrap().len(), 1); + assert_eq!(registry.history("testnet").unwrap().len(), 2); + assert_eq!(registry.history("testnet").unwrap()[0], object_id!("0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555")); + assert_eq!(registry.history("testnet").unwrap()[1], object_id!("0x3403da7ec4cd2ff9bdf6f34c0b8df5a2bd62c798089feb0d2ebf1c2e953296dc")); + assert_eq!(registry.package_id("mainnet"), Some(object_id!("0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"))); + assert_eq!(registry.package_id("testnet"), Some(object_id!("0x3403da7ec4cd2ff9bdf6f34c0b8df5a2bd62c798089feb0d2ebf1c2e953296dc"))); + assert_eq!(registry.package_id("devnet"), Some(object_id!("0x6a976d3da90db5d27f8a0c13b3268a37e582b455cfc7bf72d6461f6e8f668823"))); + } + + #[test] + fn deserialize_package_registry_from_invalid_toml() { + let invalid_toml = r#" + [aliases] + testnet = "2304aa97" + + [envs] + 2304aa97 = "invalid_value" + "#; + let result = PackageRegistry::from_package_history_toml_str(invalid_toml); + assert!(result.is_err()); + } + + #[test] + fn package_id_returns_correct_id() { + let registry = PackageRegistry::from_package_history_toml_str(PACKAGE_HISTORY_TOML).unwrap(); + let package_id = registry.package_id("mainnet"); + assert_eq!( + package_id, + Some(object_id!( + "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" + )) + ); + } + + #[test] + fn test_serialize_package_registry_to_toml() { + let mut registry = PackageRegistry::default(); + // Add well-known networks. + registry.insert_env( + Env::new_with_alias("6364aad5", "mainnet"), + vec![object_id!( + "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" + )], + ); + registry.insert_env( + Env::new_with_alias("2304aa97", "testnet"), + vec![ + object_id!("0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555"), + object_id!("0x3403da7ec4cd2ff9bdf6f34c0b8df5a2bd62c798089feb0d2ebf1c2e953296dc"), + ], + ); + registry.insert_env( + Env::new_with_alias("e678123a", "devnet"), + vec![ + object_id!("0xe6fa03d273131066036f1d2d4c3d919b9abbca93910769f26a924c7a01811103"), + object_id!("0x6a976d3da90db5d27f8a0c13b3268a37e582b455cfc7bf72d6461f6e8f668823"), + ], + ); + + let toml = toml::to_string(®istry).unwrap(); + let _ = PackageRegistry::from_package_history_toml_str(toml.as_str()) + .expect("Serialized toml string can be deserialized back to PackageRegistry"); + } + + #[test] + fn package_id_returns_none_for_unknown_chain() { + let registry = PackageRegistry::from_package_history_toml_str(PACKAGE_HISTORY_TOML).unwrap(); + let package_id = registry.package_id("unknown_chain"); + assert_eq!(package_id, None); + } + + #[test] + fn chain_alias_returns_none_for_unknown_chain_id() { + let registry = PackageRegistry::from_package_history_toml_str(PACKAGE_HISTORY_TOML).unwrap(); + let alias = registry.chain_alias("unknown_chain_id"); + assert_eq!(alias, None); + } + + #[test] + fn insert_env_overwrites_existing_alias() { + let mut registry = PackageRegistry::default(); + registry.insert_env( + Env::new_with_alias("6364aad5", "mainnet"), + vec![object_id!("0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08")], + ); + registry.insert_env( + Env::new_with_alias("2304aa97", "mainnet"), + vec![object_id!("0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555")], + ); + assert_eq!(registry.aliases.get("mainnet"), Some(&"2304aa97".to_string())); + } + + #[test] + fn insert_new_package_version_does_not_duplicate_last_version() { + let mut registry = PackageRegistry::default(); + registry.insert_new_package_version( + "6364aad5", + object_id!("0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"), + ); + registry.insert_new_package_version( + "6364aad5", + object_id!("0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"), + ); + assert_eq!( + registry.history("6364aad5").unwrap(), + &[object_id!("0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08")] + ); + } + + #[test] + fn join_merges_aliases_and_envs() { + let mut registry1 = PackageRegistry::default(); + registry1.insert_env( + Env::new_with_alias("6364aad5", "mainnet"), + vec![object_id!("0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08")], + ); + + let mut registry2 = PackageRegistry::default(); + registry2.insert_env( + Env::new_with_alias("2304aa97", "testnet"), + vec![object_id!("0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555")], + ); + + registry1.join(registry2); + + assert_eq!(registry1.aliases.get("mainnet"), Some(&"6364aad5".to_string())); + assert_eq!(registry1.aliases.get("testnet"), Some(&"2304aa97".to_string())); + assert!(registry1.envs.contains_key("6364aad5")); + assert!(registry1.envs.contains_key("2304aa97")); + } +} From 565a4b3947b3e54fcd6a63cacdff9d8e3f10da63 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Wed, 16 Jul 2025 14:46:00 +0200 Subject: [PATCH 02/28] New package-history CLI tool to automatically create and update `Move.package-history.toml` files --- Cargo.toml | 1 + package_history_cli/Cargo.toml | 18 ++ package_history_cli/src/main.rs | 287 +++++++++++++++++++++++++++++++ package_history_cli/src/utils.rs | 6 + 4 files changed, 312 insertions(+) create mode 100644 package_history_cli/Cargo.toml create mode 100644 package_history_cli/src/main.rs create mode 100644 package_history_cli/src/utils.rs diff --git a/Cargo.toml b/Cargo.toml index 866f08b..ddbb177 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "iota_interaction", "bindings/wasm/iota_interaction_ts", "iota_interaction_rust", + "package_history_cli", ] [workspace.dependencies] diff --git a/package_history_cli/Cargo.toml b/package_history_cli/Cargo.toml new file mode 100644 index 0000000..5324cf4 --- /dev/null +++ b/package_history_cli/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "package_history_cli" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "package-history" +path = "src/main.rs" + +[dependencies] +clap = { version = "4.0", features = ["derive"] } +anyhow = "1.0" +toml = "0.8" +chrono = { version = "0.4", features = ["serde"] } +product_common = { path = "../product_common" } + +[dev-dependencies] +tempfile = "3.0" \ No newline at end of file diff --git a/package_history_cli/src/main.rs b/package_history_cli/src/main.rs new file mode 100644 index 0000000..5c9c7b1 --- /dev/null +++ b/package_history_cli/src/main.rs @@ -0,0 +1,287 @@ +use anyhow::{Context, Result}; +use chrono::Utc; +use clap::{Parser, Subcommand}; +use product_common::package_registry::PackageRegistry; +use std::fs; +use std::path::{Path, PathBuf}; + +#[derive(Parser)] +#[command(name = "package-history")] +#[command(about = "A CLI tool for managing Move package history files")] +#[command(version = "1.0")] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Create an initial Move.package-history.toml file from a Move.lock file + Init { + /// Path to the Move.lock file + #[arg(short, long, default_value = "Move.lock")] + move_lock: PathBuf, + /// Output path for the Move.package-history.toml file + #[arg(short, long, default_value = "Move.package-history.toml")] + output: PathBuf, + }, + /// Add a new package version to an existing Move.package-history.toml file + Update { + /// Path to the existing Move.package-history.toml file + #[arg(short('f'), long, default_value = "Move.package-history.toml")] + history_file: PathBuf, + /// Path to the Move.lock file containing the new version + #[arg(short, long)] + move_lock: Option, + }, +} + +fn main() -> Result<()> { + let cli = Cli::parse(); + + match cli.command { + Commands::Init { move_lock, output } => { + init_package_history(&move_lock, &output)?; + } + Commands::Update { + history_file, + move_lock, + } => { + let move_lock_path = move_lock.unwrap_or_else(|| { + history_file + .parent() + .unwrap_or(Path::new(".")) + .join("Move.lock") + }); + update_package_history(&history_file, &move_lock_path)?; + } + } + + Ok(()) +} + +/// Creates an initial Move.package-history.toml file from a Move.lock file +fn init_package_history(move_lock_path: &Path, output_path: &Path) -> Result<()> { + let move_lock_content = fs::read_to_string(move_lock_path) + .with_context(|| format!("Failed to read Move.lock file: {}", move_lock_path.display()))?; + + let registry = PackageRegistry::from_move_lock_content(&move_lock_content) + .context("Failed to parse Move.lock file")?; + + let toml_content = toml::to_string(®istry) + .context("Failed to serialize PackageRegistry to TOML")?; + + fs::write(output_path, toml_content) + .with_context(|| format!("Failed to write to output file: {}", output_path.display()))?; + + println!( + "Successfully created Move.package-history.toml from {}", + move_lock_path.display() + ); + + Ok(()) +} + +/// Updates an existing Move.package-history.toml file with new package versions from a Move.lock file +fn update_package_history(history_file_path: &Path, move_lock_path: &Path) -> Result<()> { + // Read and deserialize existing package history + let history_content = fs::read_to_string(history_file_path).with_context(|| { + format!( + "Failed to read Move.package-history.toml file: {}", + history_file_path.display() + ) + })?; + + let mut registry = PackageRegistry::from_package_history_toml_str(&history_content) + .context("Failed to parse existing Move.package-history.toml file")?; + + // Create backup file + create_backup_file(history_file_path)?; + + // Read and parse Move.lock file + let move_lock_content = fs::read_to_string(move_lock_path) + .with_context(|| format!("Failed to read Move.lock file: {}", move_lock_path.display()))?; + + let new_registry = PackageRegistry::from_move_lock_content(&move_lock_content) + .context("Failed to parse Move.lock file")?; + + // Add new package versions from Move.lock to existing registry + for (chain_id, versions) in new_registry.envs().iter() { + if let Some(latest_version) = versions.last() { + registry.insert_new_package_version(chain_id, *latest_version); + } + } + + // Serialize and write updated registry + let updated_toml_content = toml::to_string(®istry) + .context("Failed to serialize updated PackageRegistry to TOML")?; + + fs::write(history_file_path, updated_toml_content).with_context(|| { + format!( + "Failed to write updated content to: {}", + history_file_path.display() + ) + })?; + + println!( + "Successfully updated {} with new versions from {}", + history_file_path.display(), + move_lock_path.display() + ); + + Ok(()) +} + +/// Creates a backup file with timestamp +fn create_backup_file(original_path: &Path) -> Result<()> { + let timestamp = Utc::now().format("%Y%m%d_%H%M%S").to_string(); + let backup_path = original_path.with_extension(format!("toml.bak-{}", timestamp)); + + fs::copy(original_path, &backup_path).with_context(|| { + format!( + "Failed to create backup file: {}", + backup_path.display() + ) + })?; + + println!("Created backup file: {}", backup_path.display()); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use tempfile::TempDir; + + fn create_test_move_lock() -> String { + r#" +[env.mainnet] +chain-id = "6364aad5" +original-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" + +[env.testnet] +chain-id = "2304aa97" +original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555" +"#.to_string() + } + + fn create_test_package_history() -> String { + r#" +[aliases] +mainnet = "6364aad5" +testnet = "2304aa97" + +[envs] +6364aad5 = ["0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"] +2304aa97 = ["0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555"] +"#.to_string() + } + + #[test] + fn init_creates_package_history_from_move_lock() { + let temp_dir = TempDir::new().unwrap(); + let move_lock_path = temp_dir.path().join("Move.lock"); + let output_path = temp_dir.path().join("Move.package-history.toml"); + + fs::write(&move_lock_path, create_test_move_lock()).unwrap(); + + init_package_history(&move_lock_path, &output_path).unwrap(); + + assert!(output_path.exists()); + let content = fs::read_to_string(&output_path).unwrap(); + assert!(content.contains("[aliases]")); + assert!(content.contains("[envs]")); + } + + #[test] + fn init_fails_with_nonexistent_move_lock() { + let temp_dir = TempDir::new().unwrap(); + let move_lock_path = temp_dir.path().join("nonexistent.lock"); + let output_path = temp_dir.path().join("output.toml"); + + let result = init_package_history(&move_lock_path, &output_path); + assert!(result.is_err()); + } + + #[test] + fn update_adds_new_package_versions() { + let temp_dir = TempDir::new().unwrap(); + let history_path = temp_dir.path().join("Move.package-history.toml"); + let move_lock_path = temp_dir.path().join("Move.lock"); + + fs::write(&history_path, create_test_package_history()).unwrap(); + + let updated_move_lock = r#" +[env.mainnet] +chain-id = "6364aad5" +original-published-id = "0x94cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de09" + +[env.testnet] +chain-id = "2304aa97" +original-published-id = "0x332741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc666" +"#; + fs::write(&move_lock_path, updated_move_lock).unwrap(); + + update_package_history(&history_path, &move_lock_path).unwrap(); + + let updated_content = fs::read_to_string(&history_path).unwrap(); + let registry = PackageRegistry::from_package_history_toml_str(&updated_content).unwrap(); + + assert_eq!(registry.history("6364aad5").unwrap().len(), 2); + assert_eq!(registry.history("2304aa97").unwrap().len(), 2); + } + + #[test] + fn update_creates_backup_file() { + let temp_dir = TempDir::new().unwrap(); + let history_path = temp_dir.path().join("Move.package-history.toml"); + let move_lock_path = temp_dir.path().join("Move.lock"); + + fs::write(&history_path, create_test_package_history()).unwrap(); + fs::write(&move_lock_path, create_test_move_lock()).unwrap(); + + update_package_history(&history_path, &move_lock_path).unwrap(); + + let backup_files: Vec<_> = fs::read_dir(temp_dir.path()) + .unwrap() + .filter_map(|entry| entry.ok()) + .filter(|entry| { + entry.file_name().to_string_lossy().starts_with("Move.package-history.toml.bak-") + }) + .collect(); + + assert_eq!(backup_files.len(), 1); + } + + #[test] + fn update_fails_with_nonexistent_history_file() { + let temp_dir = TempDir::new().unwrap(); + let history_path = temp_dir.path().join("nonexistent.toml"); + let move_lock_path = temp_dir.path().join("Move.lock"); + + fs::write(&move_lock_path, create_test_move_lock()).unwrap(); + + let result = update_package_history(&history_path, &move_lock_path); + assert!(result.is_err()); + } + + #[test] + fn update_does_not_duplicate_same_package_version() { + let temp_dir = TempDir::new().unwrap(); + let history_path = temp_dir.path().join("Move.package-history.toml"); + let move_lock_path = temp_dir.path().join("Move.lock"); + + fs::write(&history_path, create_test_package_history()).unwrap(); + fs::write(&move_lock_path, create_test_move_lock()).unwrap(); + + update_package_history(&history_path, &move_lock_path).unwrap(); + + let updated_content = fs::read_to_string(&history_path).unwrap(); + let registry = PackageRegistry::from_package_history_toml_str(&updated_content).unwrap(); + + // Should still have only 1 version each since we're adding the same versions + assert_eq!(registry.history("6364aad5").unwrap().len(), 1); + assert_eq!(registry.history("2304aa97").unwrap().len(), 1); + } +} \ No newline at end of file diff --git a/package_history_cli/src/utils.rs b/package_history_cli/src/utils.rs new file mode 100644 index 0000000..50afeb7 --- /dev/null +++ b/package_history_cli/src/utils.rs @@ -0,0 +1,6 @@ +impl PackageRegistry { + /// Exposes envs for CLI usage + pub fn envs(&self) -> &std::collections::HashMap> { + &self.envs + } +} \ No newline at end of file From 0692dbe36a51bdbee99c8a911cf3de345920c249 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 28 Jul 2025 21:27:22 +0200 Subject: [PATCH 03/28] Use JSON format instead of TOML for the Move.package-history file --- Cargo.toml | 1 + package_history_cli/Cargo.toml | 8 +- package_history_cli/src/main.rs | 392 +++++++++++++------------ product_common/Cargo.toml | 5 +- product_common/src/package_registry.rs | 250 +++++++++------- 5 files changed, 354 insertions(+), 302 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0635913..6d24d33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ serde_json = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } thiserror = { version = "1.0", default-features = false } tokio = { version = "1.44.2", default-features = false, features = ["process"] } +toml = "0.8" [workspace.lints.clippy] result_large_err = "allow" diff --git a/package_history_cli/Cargo.toml b/package_history_cli/Cargo.toml index 5324cf4..a34e8f0 100644 --- a/package_history_cli/Cargo.toml +++ b/package_history_cli/Cargo.toml @@ -8,11 +8,11 @@ name = "package-history" path = "src/main.rs" [dependencies] -clap = { version = "4.0", features = ["derive"] } anyhow = "1.0" -toml = "0.8" chrono = { version = "0.4", features = ["serde"] } -product_common = { path = "../product_common" } +clap = { version = "4.0", features = ["derive"] } +product_common = { path = "../product_common", features = ["package-reg-move-lock"] } +serde_json.workspace = true [dev-dependencies] -tempfile = "3.0" \ No newline at end of file +tempfile = "3.0" diff --git a/package_history_cli/src/main.rs b/package_history_cli/src/main.rs index 5c9c7b1..03dfb5a 100644 --- a/package_history_cli/src/main.rs +++ b/package_history_cli/src/main.rs @@ -1,161 +1,158 @@ +// Copyright 2020-2025 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::fs; +use std::path::{Path, PathBuf}; + use anyhow::{Context, Result}; use chrono::Utc; use clap::{Parser, Subcommand}; use product_common::package_registry::PackageRegistry; -use std::fs; -use std::path::{Path, PathBuf}; #[derive(Parser)] #[command(name = "package-history")] #[command(about = "A CLI tool for managing Move package history files")] #[command(version = "1.0")] struct Cli { - #[command(subcommand)] - command: Commands, + #[command(subcommand)] + command: Commands, } #[derive(Subcommand)] enum Commands { - /// Create an initial Move.package-history.toml file from a Move.lock file - Init { - /// Path to the Move.lock file - #[arg(short, long, default_value = "Move.lock")] - move_lock: PathBuf, - /// Output path for the Move.package-history.toml file - #[arg(short, long, default_value = "Move.package-history.toml")] - output: PathBuf, - }, - /// Add a new package version to an existing Move.package-history.toml file - Update { - /// Path to the existing Move.package-history.toml file - #[arg(short('f'), long, default_value = "Move.package-history.toml")] - history_file: PathBuf, - /// Path to the Move.lock file containing the new version - #[arg(short, long)] - move_lock: Option, - }, + /// Create an initial Move.package-history.json file from a Move.lock file + Init { + /// Path to the Move.lock file + #[arg(short, long, default_value = "Move.lock")] + move_lock: PathBuf, + /// Output path for the Move.package-history.json file + #[arg(short, long, default_value = "Move.package-history.json")] + output: PathBuf, + }, + /// Add a new package version to an existing Move.package-history.json file + Update { + /// Path to the existing Move.package-history.json file + #[arg(short('f'), long, default_value = "Move.package-history.json")] + history_file: PathBuf, + /// Path to the Move.lock file containing the new version + #[arg(short, long)] + move_lock: Option, + }, } fn main() -> Result<()> { - let cli = Cli::parse(); - - match cli.command { - Commands::Init { move_lock, output } => { - init_package_history(&move_lock, &output)?; - } - Commands::Update { - history_file, - move_lock, - } => { - let move_lock_path = move_lock.unwrap_or_else(|| { - history_file - .parent() - .unwrap_or(Path::new(".")) - .join("Move.lock") - }); - update_package_history(&history_file, &move_lock_path)?; - } + let cli = Cli::parse(); + + match cli.command { + Commands::Init { move_lock, output } => { + init_package_history(&move_lock, &output)?; + } + Commands::Update { + history_file, + move_lock, + } => { + let move_lock_path = + move_lock.unwrap_or_else(|| history_file.parent().unwrap_or(Path::new(".")).join("Move.lock")); + update_package_history(&history_file, &move_lock_path)?; } + } - Ok(()) + Ok(()) } -/// Creates an initial Move.package-history.toml file from a Move.lock file +/// Creates an initial Move.package-history.json file from a Move.lock file fn init_package_history(move_lock_path: &Path, output_path: &Path) -> Result<()> { - let move_lock_content = fs::read_to_string(move_lock_path) - .with_context(|| format!("Failed to read Move.lock file: {}", move_lock_path.display()))?; + let move_lock_content = fs::read_to_string(move_lock_path) + .with_context(|| format!("Failed to read Move.lock file: {}", move_lock_path.display()))?; + + let registry = + PackageRegistry::from_move_lock_content(&move_lock_content).context("Failed to parse Move.lock file")?; - let registry = PackageRegistry::from_move_lock_content(&move_lock_content) - .context("Failed to parse Move.lock file")?; + let json_content = to_prettified_string(®istry)?; - let toml_content = toml::to_string(®istry) - .context("Failed to serialize PackageRegistry to TOML")?; + fs::write(output_path, json_content) + .with_context(|| format!("Failed to write to output file: {}", output_path.display()))?; - fs::write(output_path, toml_content) - .with_context(|| format!("Failed to write to output file: {}", output_path.display()))?; + println!( + "Successfully created Move.package-history.json from {}", + move_lock_path.display() + ); - println!( - "Successfully created Move.package-history.toml from {}", - move_lock_path.display() - ); + Ok(()) +} - Ok(()) +fn to_prettified_string(registry: &PackageRegistry) -> Result { + let json_value = serde_json::to_value(registry).context("Failed to serialize PackageRegistry to JSON value")?; + Ok(format!("{json_value:#}")) } -/// Updates an existing Move.package-history.toml file with new package versions from a Move.lock file +/// Updates an existing Move.package-history.json file with new package versions from a Move.lock file fn update_package_history(history_file_path: &Path, move_lock_path: &Path) -> Result<()> { - // Read and deserialize existing package history - let history_content = fs::read_to_string(history_file_path).with_context(|| { - format!( - "Failed to read Move.package-history.toml file: {}", - history_file_path.display() - ) - })?; - - let mut registry = PackageRegistry::from_package_history_toml_str(&history_content) - .context("Failed to parse existing Move.package-history.toml file")?; - - // Create backup file - create_backup_file(history_file_path)?; - - // Read and parse Move.lock file - let move_lock_content = fs::read_to_string(move_lock_path) - .with_context(|| format!("Failed to read Move.lock file: {}", move_lock_path.display()))?; - - let new_registry = PackageRegistry::from_move_lock_content(&move_lock_content) - .context("Failed to parse Move.lock file")?; - - // Add new package versions from Move.lock to existing registry - for (chain_id, versions) in new_registry.envs().iter() { - if let Some(latest_version) = versions.last() { - registry.insert_new_package_version(chain_id, *latest_version); - } + // Read and deserialize existing package history + let history_content = fs::read_to_string(history_file_path).with_context(|| { + format!( + "Failed to read Move.package-history.json file: {}", + history_file_path.display() + ) + })?; + + let mut registry = PackageRegistry::from_package_history_json_str(&history_content) + .context("Failed to parse existing Move.package-history.json file")?; + + // Create backup file + create_backup_file(history_file_path)?; + + // Read and parse Move.lock file + let move_lock_content = fs::read_to_string(move_lock_path) + .with_context(|| format!("Failed to read Move.lock file: {}", move_lock_path.display()))?; + + let new_registry = + PackageRegistry::from_move_lock_content(&move_lock_content).context("Failed to parse Move.lock file")?; + + // Add new package versions from Move.lock to existing registry + for (chain_id, versions) in new_registry.envs().iter() { + if let Some(latest_version) = versions.last() { + registry.insert_new_package_version(chain_id, *latest_version); } + } - // Serialize and write updated registry - let updated_toml_content = toml::to_string(®istry) - .context("Failed to serialize updated PackageRegistry to TOML")?; + // Serialize and write updated registry + let updated_json_content = to_prettified_string(®istry)?; - fs::write(history_file_path, updated_toml_content).with_context(|| { - format!( - "Failed to write updated content to: {}", - history_file_path.display() - ) - })?; + fs::write(history_file_path, updated_json_content) + .with_context(|| format!("Failed to write updated content to: {}", history_file_path.display()))?; - println!( - "Successfully updated {} with new versions from {}", - history_file_path.display(), - move_lock_path.display() - ); + println!( + "Successfully updated {} with new versions from {}", + history_file_path.display(), + move_lock_path.display() + ); - Ok(()) + Ok(()) } /// Creates a backup file with timestamp fn create_backup_file(original_path: &Path) -> Result<()> { - let timestamp = Utc::now().format("%Y%m%d_%H%M%S").to_string(); - let backup_path = original_path.with_extension(format!("toml.bak-{}", timestamp)); - - fs::copy(original_path, &backup_path).with_context(|| { - format!( - "Failed to create backup file: {}", - backup_path.display() - ) - })?; - - println!("Created backup file: {}", backup_path.display()); - Ok(()) + let timestamp = Utc::now().format("%Y%m%d_%H%M%S").to_string(); + let backup_path = original_path.with_extension(format!("json.bak-{timestamp}")); + + fs::copy(original_path, &backup_path) + .with_context(|| format!("Failed to create backup file: {}", backup_path.display()))?; + + println!("Created backup file: {}", backup_path.display()); + Ok(()) } #[cfg(test)] mod tests { - use super::*; - use std::fs; - use tempfile::TempDir; + use std::fs; - fn create_test_move_lock() -> String { - r#" + use tempfile::TempDir; + + use super::*; + + fn create_test_move_lock() -> String { + r#" [env.mainnet] chain-id = "6364aad5" original-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" @@ -163,11 +160,12 @@ original-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed20 [env.testnet] chain-id = "2304aa97" original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555" -"#.to_string() - } +"# + .to_string() + } - fn create_test_package_history() -> String { - r#" + fn create_test_package_history() -> String { + r#" [aliases] mainnet = "6364aad5" testnet = "2304aa97" @@ -175,44 +173,45 @@ testnet = "2304aa97" [envs] 6364aad5 = ["0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"] 2304aa97 = ["0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555"] -"#.to_string() - } +"# + .to_string() + } - #[test] - fn init_creates_package_history_from_move_lock() { - let temp_dir = TempDir::new().unwrap(); - let move_lock_path = temp_dir.path().join("Move.lock"); - let output_path = temp_dir.path().join("Move.package-history.toml"); + #[test] + fn init_creates_package_history_from_move_lock() { + let temp_dir = TempDir::new().unwrap(); + let move_lock_path = temp_dir.path().join("Move.lock"); + let output_path = temp_dir.path().join("Move.package-history.json"); - fs::write(&move_lock_path, create_test_move_lock()).unwrap(); + fs::write(&move_lock_path, create_test_move_lock()).unwrap(); - init_package_history(&move_lock_path, &output_path).unwrap(); + init_package_history(&move_lock_path, &output_path).unwrap(); - assert!(output_path.exists()); - let content = fs::read_to_string(&output_path).unwrap(); - assert!(content.contains("[aliases]")); - assert!(content.contains("[envs]")); - } + assert!(output_path.exists()); + let content = fs::read_to_string(&output_path).unwrap(); + assert!(content.contains("[aliases]")); + assert!(content.contains("[envs]")); + } - #[test] - fn init_fails_with_nonexistent_move_lock() { - let temp_dir = TempDir::new().unwrap(); - let move_lock_path = temp_dir.path().join("nonexistent.lock"); - let output_path = temp_dir.path().join("output.toml"); + #[test] + fn init_fails_with_nonexistent_move_lock() { + let temp_dir = TempDir::new().unwrap(); + let move_lock_path = temp_dir.path().join("nonexistent.lock"); + let output_path = temp_dir.path().join("output.json"); - let result = init_package_history(&move_lock_path, &output_path); - assert!(result.is_err()); - } + let result = init_package_history(&move_lock_path, &output_path); + assert!(result.is_err()); + } - #[test] - fn update_adds_new_package_versions() { - let temp_dir = TempDir::new().unwrap(); - let history_path = temp_dir.path().join("Move.package-history.toml"); - let move_lock_path = temp_dir.path().join("Move.lock"); + #[test] + fn update_adds_new_package_versions() { + let temp_dir = TempDir::new().unwrap(); + let history_path = temp_dir.path().join("Move.package-history.json"); + let move_lock_path = temp_dir.path().join("Move.lock"); - fs::write(&history_path, create_test_package_history()).unwrap(); + fs::write(&history_path, create_test_package_history()).unwrap(); - let updated_move_lock = r#" + let updated_move_lock = r#" [env.mainnet] chain-id = "6364aad5" original-published-id = "0x94cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de09" @@ -221,67 +220,70 @@ original-published-id = "0x94cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed20 chain-id = "2304aa97" original-published-id = "0x332741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc666" "#; - fs::write(&move_lock_path, updated_move_lock).unwrap(); + fs::write(&move_lock_path, updated_move_lock).unwrap(); - update_package_history(&history_path, &move_lock_path).unwrap(); + update_package_history(&history_path, &move_lock_path).unwrap(); - let updated_content = fs::read_to_string(&history_path).unwrap(); - let registry = PackageRegistry::from_package_history_toml_str(&updated_content).unwrap(); + let updated_content = fs::read_to_string(&history_path).unwrap(); + let registry = PackageRegistry::from_package_history_json_str(&updated_content).unwrap(); - assert_eq!(registry.history("6364aad5").unwrap().len(), 2); - assert_eq!(registry.history("2304aa97").unwrap().len(), 2); - } + assert_eq!(registry.history("6364aad5").unwrap().len(), 2); + assert_eq!(registry.history("2304aa97").unwrap().len(), 2); + } - #[test] - fn update_creates_backup_file() { - let temp_dir = TempDir::new().unwrap(); - let history_path = temp_dir.path().join("Move.package-history.toml"); - let move_lock_path = temp_dir.path().join("Move.lock"); + #[test] + fn update_creates_backup_file() { + let temp_dir = TempDir::new().unwrap(); + let history_path = temp_dir.path().join("Move.package-history.json"); + let move_lock_path = temp_dir.path().join("Move.lock"); - fs::write(&history_path, create_test_package_history()).unwrap(); - fs::write(&move_lock_path, create_test_move_lock()).unwrap(); + fs::write(&history_path, create_test_package_history()).unwrap(); + fs::write(&move_lock_path, create_test_move_lock()).unwrap(); - update_package_history(&history_path, &move_lock_path).unwrap(); + update_package_history(&history_path, &move_lock_path).unwrap(); - let backup_files: Vec<_> = fs::read_dir(temp_dir.path()) - .unwrap() - .filter_map(|entry| entry.ok()) - .filter(|entry| { - entry.file_name().to_string_lossy().starts_with("Move.package-history.toml.bak-") - }) - .collect(); + let backup_files: Vec<_> = fs::read_dir(temp_dir.path()) + .unwrap() + .filter_map(|entry| entry.ok()) + .filter(|entry| { + entry + .file_name() + .to_string_lossy() + .starts_with("Move.package-history.json.bak-") + }) + .collect(); - assert_eq!(backup_files.len(), 1); - } + assert_eq!(backup_files.len(), 1); + } - #[test] - fn update_fails_with_nonexistent_history_file() { - let temp_dir = TempDir::new().unwrap(); - let history_path = temp_dir.path().join("nonexistent.toml"); - let move_lock_path = temp_dir.path().join("Move.lock"); + #[test] + fn update_fails_with_nonexistent_history_file() { + let temp_dir = TempDir::new().unwrap(); + let history_path = temp_dir.path().join("nonexistent.json"); + let move_lock_path = temp_dir.path().join("Move.lock"); - fs::write(&move_lock_path, create_test_move_lock()).unwrap(); + fs::write(&move_lock_path, create_test_move_lock()).unwrap(); - let result = update_package_history(&history_path, &move_lock_path); - assert!(result.is_err()); - } + let result = update_package_history(&history_path, &move_lock_path); + assert!(result.is_err()); + } - #[test] - fn update_does_not_duplicate_same_package_version() { - let temp_dir = TempDir::new().unwrap(); - let history_path = temp_dir.path().join("Move.package-history.toml"); - let move_lock_path = temp_dir.path().join("Move.lock"); + #[test] + fn update_does_not_duplicate_same_package_version() { + let temp_dir = TempDir::new().unwrap(); + let history_path = temp_dir.path().join("Move.package-history.json"); + let move_lock_path = temp_dir.path().join("Move.lock"); - fs::write(&history_path, create_test_package_history()).unwrap(); - fs::write(&move_lock_path, create_test_move_lock()).unwrap(); + fs::write(&history_path, create_test_package_history()).unwrap(); + fs::write(&move_lock_path, create_test_move_lock()).unwrap(); - update_package_history(&history_path, &move_lock_path).unwrap(); + update_package_history(&history_path, &move_lock_path).unwrap(); - let updated_content = fs::read_to_string(&history_path).unwrap(); - let registry = PackageRegistry::from_package_history_toml_str(&updated_content).unwrap(); + let updated_content = fs::read_to_string(&history_path).unwrap(); + let registry = PackageRegistry::from_package_history_json_str(&updated_content).unwrap(); - // Should still have only 1 version each since we're adding the same versions - assert_eq!(registry.history("6364aad5").unwrap().len(), 1); - assert_eq!(registry.history("2304aa97").unwrap().len(), 1); - } -} \ No newline at end of file + // Should still have only 1 version each since we're adding the same versions + assert_eq!(registry.history("6364aad5").unwrap().len(), 1); + assert_eq!(registry.history("2304aa97").unwrap().len(), 1); + } +} diff --git a/product_common/Cargo.toml b/product_common/Cargo.toml index 1005360..086a9d9 100644 --- a/product_common/Cargo.toml +++ b/product_common/Cargo.toml @@ -27,7 +27,7 @@ serde.workspace = true serde_json.workspace = true strum.workspace = true thiserror.workspace = true -toml = "0.8" +toml = { workspace = true, optional = true } url = { version = "2", default-features = false, optional = true, features = ["serde"] } [dependencies.identity_jose] @@ -88,6 +88,9 @@ gas-station = ["transaction", "http-client"] # instead use reqwest::Client. default-http-client = ["http-client", "dep:reqwest"] +# PackageRegistry with Move.lock reading capabilities. +package-reg-move-lock = ["dep:toml"] + [package.metadata.docs.rs] # To build locally: # RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps --workspace --open diff --git a/product_common/src/package_registry.rs b/product_common/src/package_registry.rs index 597d196..e2a2283 100644 --- a/product_common/src/package_registry.rs +++ b/product_common/src/package_registry.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; use anyhow::Context; use iota_interaction::types::base_types::ObjectID; use serde::{Deserialize, Deserializer, Serialize}; +use serde_json::Value; pub const MAINNET_CHAIN_ID: &str = "6364aad5"; @@ -75,7 +76,7 @@ impl PackageRegistry { /// Returns the envs of this package registry. pub fn envs(&self) -> &HashMap> { - &self.envs + &self.envs } /// Adds or replaces this package's metadata for a given environment. @@ -94,7 +95,7 @@ impl PackageRegistry { if history.last() != Some(&package) { history.push(package) } - } + } /// Merges another [PackageRegistry] into this one. pub fn join(&mut self, other: PackageRegistry) { @@ -102,42 +103,52 @@ impl PackageRegistry { self.envs.extend(other.envs); } - /// Creates a [PackageRegistry] from a Move.package-history.toml file. - pub fn from_package_history_toml_str(package_history: &str) -> anyhow::Result { - let mut package_history: toml::Table = package_history.parse()?; - + /// Creates a [PackageRegistry] from a Move.package-history.json file. + pub fn from_package_history_json_str(package_history: &str) -> anyhow::Result { + let package_history: Value = serde_json::from_str(package_history)?; + let ret_val = package_history - .remove("aliases") - .context("invalid Move.package-history.toml file: missing `aliases` table")? - .as_table_mut() - .map(std::mem::take) - .context("invalid Move.package-history.toml file: `aliases` is not a table")? + .get("aliases") + .context("invalid Move.package-history.json file: missing `aliases` object")? + .as_object() + .context("invalid Move.package-history.json file: `aliases` is not a JSON object literal")? .into_iter() .try_fold(Self::default(), |mut registry, (alias, chain_id)| { let chain_id: String = chain_id - .clone() - .try_into() - .context(format!("invalid Move.package-history.toml file: invalid `chain-id` '{chain_id}' for alias {alias}"))?; - registry.aliases.insert(alias, chain_id); + .as_str() + .context(format!( + "invalid Move.package-history.json file: invalid `chain-id` '{chain_id}' for alias {alias}" + ))? + .to_string(); + registry.aliases.insert(alias.clone(), chain_id); Ok::(registry) })?; package_history - .remove("envs") - .context("invalid Move.package-history.toml file: missing `envs` table")? - .as_table_mut() - .map(std::mem::take) - .context("invalid Move.package-history.toml file: `envs` is not a table")? + .get("envs") + .context("invalid Move.package-history.json file: missing `envs` object")? + .as_object() + .context("invalid Move.package-history.json file: `envs` is not a JSON object literal")? .into_iter() .try_fold(ret_val, |mut registry, (chain_id, versions)| { let versions: Vec = versions - .try_into() - .context(format!("invalid Move.package-history.toml file: invalid versions for {chain_id}"))?; - registry.envs.insert(chain_id, versions); + .as_array() + .context(format!("invalid Move.package-history.json file: invalid versions for {chain_id}. versions is not an array"))? + .iter() + .try_fold(Vec::::new(), |mut arr, v| { + let obj_id = ObjectID::from_hex_literal( + v.as_str() + .context(format!("invalid Move.package-history.json file: invalid versions array element for {chain_id}. Elements need to be strings"))? + )?; + arr.push(obj_id); + Ok::, anyhow::Error>(arr) + })?; + registry.envs.insert(chain_id.clone(), versions); Ok(registry) }) } + #[cfg(feature = "package-reg-move-lock")] /// Creates a [PackageRegistry] from a Move.lock file. pub fn from_move_lock_content(move_lock: &str) -> anyhow::Result { let mut move_lock: toml::Table = move_lock.parse()?; @@ -150,41 +161,47 @@ impl PackageRegistry { .context("invalid Move.lock file: `env` is not a table")? .into_iter(); - move_lock_iter - .try_fold(Self::default(), |mut registry, (alias, table)| { - let toml::Value::Table(mut table) = table else { - anyhow::bail!("invalid Move.lock file: invalid `env` table"); - }; - let chain_id: String = table - .remove("chain-id") - .context(format!("invalid Move.lock file: missing `chain-id` for env {alias}"))? - .try_into() - .context("invalid Move.lock file: invalid `chain-id`")?; - - let original_published_id: String = remove_first_and_last_char_from_string( - table.get("original-published-id") - .context(format!("invalid Move.lock file: missing `original-published-id` for env {alias}"))? - .to_string() - ); - let latest_published_id: String = remove_first_and_last_char_from_string( - table.get("latest-published-id") - .context(format!("invalid Move.lock file: missing `latest-published-id` for env {alias}"))? - .to_string() - ); - - let mut metadata = vec![ObjectID::from_hex_literal(original_published_id.as_str())?]; - if original_published_id != latest_published_id { - metadata.push(ObjectID::from_hex_literal(latest_published_id.as_str())?); - } - - let env = Env::new_with_alias(chain_id, alias.clone()); - registry.insert_env(env, metadata); - - Ok(registry) - }) + move_lock_iter.try_fold(Self::default(), |mut registry, (alias, table)| { + let toml::Value::Table(mut table) = table else { + anyhow::bail!("invalid Move.lock file: invalid `env` table"); + }; + let chain_id: String = table + .remove("chain-id") + .context(format!("invalid Move.lock file: missing `chain-id` for env {alias}"))? + .try_into() + .context("invalid Move.lock file: invalid `chain-id`")?; + + let original_published_id: String = remove_first_and_last_char_from_string( + table + .get("original-published-id") + .context(format!( + "invalid Move.lock file: missing `original-published-id` for env {alias}" + ))? + .to_string(), + ); + let latest_published_id: String = remove_first_and_last_char_from_string( + table + .get("latest-published-id") + .context(format!( + "invalid Move.lock file: missing `latest-published-id` for env {alias}" + ))? + .to_string(), + ); + + let mut metadata = vec![ObjectID::from_hex_literal(original_published_id.as_str())?]; + if original_published_id != latest_published_id { + metadata.push(ObjectID::from_hex_literal(latest_published_id.as_str())?); + } + + let env = Env::new_with_alias(chain_id, alias.clone()); + registry.insert_env(env, metadata); + + Ok(registry) + }) } } +#[cfg(feature = "package-reg-move-lock")] fn remove_first_and_last_char_from_string(value: String) -> String { let mut chars = value.chars(); chars.next(); @@ -201,62 +218,81 @@ mod tests { ObjectID::from_hex_literal($id).unwrap() }; } - - const PACKAGE_HISTORY_TOML: &str = r#" - [aliases] - mainnet = "6364aad5" - testnet = "2304aa97" - devnet = "e678123a" - - [envs] - 6364aad5 = ["0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"] - 2304aa97 = ["0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555", "0x3403da7ec4cd2ff9bdf6f34c0b8df5a2bd62c798089feb0d2ebf1c2e953296dc"] - e678123a = ["0xe6fa03d273131066036f1d2d4c3d919b9abbca93910769f26a924c7a01811103", "0x6a976d3da90db5d27f8a0c13b3268a37e582b455cfc7bf72d6461f6e8f668823"] - "#; + + const PACKAGE_HISTORY_JSON: &str = r#" +{ + "aliases": { + "localnet": "594fb3ed", + "devnet": "e678123a", + "testnet": "2304aa97", + "mainnet": "6364aad5" + }, + "envs": { + "6364aad5": ["0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"], + "e678123a": [ + "0xe6fa03d273131066036f1d2d4c3d919b9abbca93910769f26a924c7a01811103", + "0x6a976d3da90db5d27f8a0c13b3268a37e582b455cfc7bf72d6461f6e8f668823" + ], + "594fb3ed": ["0xd097794267324a58734ff754919f4a16461e39ed39901b29778b86a1261930ba"], + "2304aa97": [ + "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555", + "0x3403da7ec4cd2ff9bdf6f34c0b8df5a2bd62c798089feb0d2ebf1c2e953296dc" + ] + } +} +"#; #[test] - fn deserialize_package_registry_from_valid_toml() { - let registry = PackageRegistry::from_package_history_toml_str(PACKAGE_HISTORY_TOML).unwrap(); + fn deserialize_package_registry_from_valid_json() { + let registry = PackageRegistry::from_package_history_json_str(PACKAGE_HISTORY_JSON).unwrap(); assert_eq!(registry.aliases.get("mainnet"), Some(&"6364aad5".to_string())); assert_eq!(registry.aliases.get("testnet"), Some(&"2304aa97".to_string())); assert_eq!(registry.envs.get("6364aad5").unwrap().len(), 1); assert_eq!(registry.envs.get("2304aa97").unwrap().len(), 2); assert_eq!(registry.history("mainnet").unwrap().len(), 1); assert_eq!(registry.history("testnet").unwrap().len(), 2); - assert_eq!(registry.history("testnet").unwrap()[0], object_id!("0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555")); - assert_eq!(registry.history("testnet").unwrap()[1], object_id!("0x3403da7ec4cd2ff9bdf6f34c0b8df5a2bd62c798089feb0d2ebf1c2e953296dc")); - assert_eq!(registry.package_id("mainnet"), Some(object_id!("0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"))); - assert_eq!(registry.package_id("testnet"), Some(object_id!("0x3403da7ec4cd2ff9bdf6f34c0b8df5a2bd62c798089feb0d2ebf1c2e953296dc"))); - assert_eq!(registry.package_id("devnet"), Some(object_id!("0x6a976d3da90db5d27f8a0c13b3268a37e582b455cfc7bf72d6461f6e8f668823"))); - } - - #[test] - fn deserialize_package_registry_from_invalid_toml() { - let invalid_toml = r#" - [aliases] - testnet = "2304aa97" - - [envs] - 2304aa97 = "invalid_value" - "#; - let result = PackageRegistry::from_package_history_toml_str(invalid_toml); - assert!(result.is_err()); + assert_eq!( + registry.history("testnet").unwrap()[0], + object_id!("0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555") + ); + assert_eq!( + registry.history("testnet").unwrap()[1], + object_id!("0x3403da7ec4cd2ff9bdf6f34c0b8df5a2bd62c798089feb0d2ebf1c2e953296dc") + ); + assert_eq!( + registry.package_id("mainnet"), + Some(object_id!( + "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" + )) + ); + assert_eq!( + registry.package_id("testnet"), + Some(object_id!( + "0x3403da7ec4cd2ff9bdf6f34c0b8df5a2bd62c798089feb0d2ebf1c2e953296dc" + )) + ); + assert_eq!( + registry.package_id("devnet"), + Some(object_id!( + "0x6a976d3da90db5d27f8a0c13b3268a37e582b455cfc7bf72d6461f6e8f668823" + )) + ); } #[test] fn package_id_returns_correct_id() { - let registry = PackageRegistry::from_package_history_toml_str(PACKAGE_HISTORY_TOML).unwrap(); + let registry = PackageRegistry::from_package_history_json_str(PACKAGE_HISTORY_JSON).unwrap(); let package_id = registry.package_id("mainnet"); assert_eq!( package_id, Some(object_id!( - "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" - )) + "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" + )) ); } - + #[test] - fn test_serialize_package_registry_to_toml() { + fn test_serialize_package_registry_to_json() { let mut registry = PackageRegistry::default(); // Add well-known networks. registry.insert_env( @@ -280,21 +316,21 @@ mod tests { ], ); - let toml = toml::to_string(®istry).unwrap(); - let _ = PackageRegistry::from_package_history_toml_str(toml.as_str()) - .expect("Serialized toml string can be deserialized back to PackageRegistry"); + let json_content = serde_json::to_string(®istry).unwrap(); + let _ = PackageRegistry::from_package_history_json_str(json_content.as_str()) + .expect("Serialized json string can be deserialized back to PackageRegistry"); } #[test] fn package_id_returns_none_for_unknown_chain() { - let registry = PackageRegistry::from_package_history_toml_str(PACKAGE_HISTORY_TOML).unwrap(); + let registry = PackageRegistry::from_package_history_json_str(PACKAGE_HISTORY_JSON).unwrap(); let package_id = registry.package_id("unknown_chain"); assert_eq!(package_id, None); } #[test] fn chain_alias_returns_none_for_unknown_chain_id() { - let registry = PackageRegistry::from_package_history_toml_str(PACKAGE_HISTORY_TOML).unwrap(); + let registry = PackageRegistry::from_package_history_json_str(PACKAGE_HISTORY_JSON).unwrap(); let alias = registry.chain_alias("unknown_chain_id"); assert_eq!(alias, None); } @@ -304,11 +340,15 @@ mod tests { let mut registry = PackageRegistry::default(); registry.insert_env( Env::new_with_alias("6364aad5", "mainnet"), - vec![object_id!("0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08")], + vec![object_id!( + "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" + )], ); registry.insert_env( Env::new_with_alias("2304aa97", "mainnet"), - vec![object_id!("0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555")], + vec![object_id!( + "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555" + )], ); assert_eq!(registry.aliases.get("mainnet"), Some(&"2304aa97".to_string())); } @@ -326,7 +366,9 @@ mod tests { ); assert_eq!( registry.history("6364aad5").unwrap(), - &[object_id!("0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08")] + &[object_id!( + "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" + )] ); } @@ -335,13 +377,17 @@ mod tests { let mut registry1 = PackageRegistry::default(); registry1.insert_env( Env::new_with_alias("6364aad5", "mainnet"), - vec![object_id!("0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08")], + vec![object_id!( + "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" + )], ); let mut registry2 = PackageRegistry::default(); registry2.insert_env( Env::new_with_alias("2304aa97", "testnet"), - vec![object_id!("0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555")], + vec![object_id!( + "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555" + )], ); registry1.join(registry2); From 3d39d3c3264db1950e22c1216d85776b1576b061 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 4 Aug 2025 15:07:59 +0200 Subject: [PATCH 04/28] Fixed package_history_cli tests --- package_history_cli/src/main.rs | 36 +++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/package_history_cli/src/main.rs b/package_history_cli/src/main.rs index 03dfb5a..22da83d 100644 --- a/package_history_cli/src/main.rs +++ b/package_history_cli/src/main.rs @@ -156,23 +156,28 @@ mod tests { [env.mainnet] chain-id = "6364aad5" original-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" +latest-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" [env.testnet] chain-id = "2304aa97" original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555" +latest-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555" "# .to_string() } fn create_test_package_history() -> String { r#" -[aliases] -mainnet = "6364aad5" -testnet = "2304aa97" - -[envs] -6364aad5 = ["0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"] -2304aa97 = ["0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555"] +{ + "aliases": { + "testnet": "2304aa97", + "mainnet": "6364aad5" + }, + "envs": { + "6364aad5": ["0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"], + "2304aa97": ["0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555"] + } +} "# .to_string() } @@ -189,8 +194,15 @@ testnet = "2304aa97" assert!(output_path.exists()); let content = fs::read_to_string(&output_path).unwrap(); - assert!(content.contains("[aliases]")); - assert!(content.contains("[envs]")); + assert!(content.contains("\"aliases\": {")); + assert!(content.contains("\"mainnet\": \"6364aad5\"")); + assert!(content.contains("\"testnet\": \"2304aa97\"")); + + assert!(content.contains("\"envs\": {")); + assert!(content.contains("\"2304aa97\": [")); + assert!(content.contains("\"0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555\"")); + assert!(content.contains("\"6364aad5\": [")); + assert!(content.contains("\"0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08\"")); } #[test] @@ -214,11 +226,13 @@ testnet = "2304aa97" let updated_move_lock = r#" [env.mainnet] chain-id = "6364aad5" -original-published-id = "0x94cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de09" +latest-published-id = "0x94cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de09" +original-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" [env.testnet] chain-id = "2304aa97" -original-published-id = "0x332741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc666" +latest-published-id = "0x332741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc666" +original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555" "#; fs::write(&move_lock_path, updated_move_lock).unwrap(); From dfc88ce237d3643994a218525af7aa32aae363bf Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 4 Aug 2025 15:31:35 +0200 Subject: [PATCH 05/28] Remove dead code --- package_history_cli/src/utils.rs | 6 ------ product_common/src/package_registry.rs | 11 +---------- 2 files changed, 1 insertion(+), 16 deletions(-) delete mode 100644 package_history_cli/src/utils.rs diff --git a/package_history_cli/src/utils.rs b/package_history_cli/src/utils.rs deleted file mode 100644 index 50afeb7..0000000 --- a/package_history_cli/src/utils.rs +++ /dev/null @@ -1,6 +0,0 @@ -impl PackageRegistry { - /// Exposes envs for CLI usage - pub fn envs(&self) -> &std::collections::HashMap> { - &self.envs - } -} \ No newline at end of file diff --git a/product_common/src/package_registry.rs b/product_common/src/package_registry.rs index e2a2283..e24da84 100644 --- a/product_common/src/package_registry.rs +++ b/product_common/src/package_registry.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use anyhow::Context; use iota_interaction::types::base_types::ObjectID; -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Serialize}; use serde_json::Value; pub const MAINNET_CHAIN_ID: &str = "6364aad5"; @@ -35,15 +35,6 @@ impl Env { } } -fn deserialize_u64_from_str<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - use serde::de::Error; - - String::deserialize(deserializer)?.parse().map_err(D::Error::custom) -} - #[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct PackageRegistry { aliases: HashMap, From 69ca4cc686d62ea3777682f80b9528731fbaa6ef Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Thu, 7 Aug 2025 15:38:37 +0200 Subject: [PATCH 06/28] Rename `Move.package-history.json` to `Move.history.json` --- package_history_cli/src/main.rs | 32 +++++++++++++------------- product_common/src/package_registry.rs | 16 ++++++------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/package_history_cli/src/main.rs b/package_history_cli/src/main.rs index 22da83d..6c5fbb1 100644 --- a/package_history_cli/src/main.rs +++ b/package_history_cli/src/main.rs @@ -20,19 +20,19 @@ struct Cli { #[derive(Subcommand)] enum Commands { - /// Create an initial Move.package-history.json file from a Move.lock file + /// Create an initial Move.history.json file from a Move.lock file Init { /// Path to the Move.lock file #[arg(short, long, default_value = "Move.lock")] move_lock: PathBuf, - /// Output path for the Move.package-history.json file - #[arg(short, long, default_value = "Move.package-history.json")] + /// Output path for the Move.history.json file + #[arg(short, long, default_value = "Move.history.json")] output: PathBuf, }, - /// Add a new package version to an existing Move.package-history.json file + /// Add a new package version to an existing Move.history.json file Update { - /// Path to the existing Move.package-history.json file - #[arg(short('f'), long, default_value = "Move.package-history.json")] + /// Path to the existing Move.history.json file + #[arg(short('f'), long, default_value = "Move.history.json")] history_file: PathBuf, /// Path to the Move.lock file containing the new version #[arg(short, long)] @@ -60,7 +60,7 @@ fn main() -> Result<()> { Ok(()) } -/// Creates an initial Move.package-history.json file from a Move.lock file +/// Creates an initial Move.history.json file from a Move.lock file fn init_package_history(move_lock_path: &Path, output_path: &Path) -> Result<()> { let move_lock_content = fs::read_to_string(move_lock_path) .with_context(|| format!("Failed to read Move.lock file: {}", move_lock_path.display()))?; @@ -74,7 +74,7 @@ fn init_package_history(move_lock_path: &Path, output_path: &Path) -> Result<()> .with_context(|| format!("Failed to write to output file: {}", output_path.display()))?; println!( - "Successfully created Move.package-history.json from {}", + "Successfully created Move.history.json from {}", move_lock_path.display() ); @@ -86,18 +86,18 @@ fn to_prettified_string(registry: &PackageRegistry) -> Result { Ok(format!("{json_value:#}")) } -/// Updates an existing Move.package-history.json file with new package versions from a Move.lock file +/// Updates an existing Move.history.json file with new package versions from a Move.lock file fn update_package_history(history_file_path: &Path, move_lock_path: &Path) -> Result<()> { // Read and deserialize existing package history let history_content = fs::read_to_string(history_file_path).with_context(|| { format!( - "Failed to read Move.package-history.json file: {}", + "Failed to read Move.history.json file: {}", history_file_path.display() ) })?; let mut registry = PackageRegistry::from_package_history_json_str(&history_content) - .context("Failed to parse existing Move.package-history.json file")?; + .context("Failed to parse existing Move.history.json file")?; // Create backup file create_backup_file(history_file_path)?; @@ -186,7 +186,7 @@ latest-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864a fn init_creates_package_history_from_move_lock() { let temp_dir = TempDir::new().unwrap(); let move_lock_path = temp_dir.path().join("Move.lock"); - let output_path = temp_dir.path().join("Move.package-history.json"); + let output_path = temp_dir.path().join("../../Move.history.json"); fs::write(&move_lock_path, create_test_move_lock()).unwrap(); @@ -218,7 +218,7 @@ latest-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864a #[test] fn update_adds_new_package_versions() { let temp_dir = TempDir::new().unwrap(); - let history_path = temp_dir.path().join("Move.package-history.json"); + let history_path = temp_dir.path().join("../../Move.history.json"); let move_lock_path = temp_dir.path().join("Move.lock"); fs::write(&history_path, create_test_package_history()).unwrap(); @@ -248,7 +248,7 @@ original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac86 #[test] fn update_creates_backup_file() { let temp_dir = TempDir::new().unwrap(); - let history_path = temp_dir.path().join("Move.package-history.json"); + let history_path = temp_dir.path().join("../../Move.history.json"); let move_lock_path = temp_dir.path().join("Move.lock"); fs::write(&history_path, create_test_package_history()).unwrap(); @@ -263,7 +263,7 @@ original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac86 entry .file_name() .to_string_lossy() - .starts_with("Move.package-history.json.bak-") + .starts_with("Move.history.json.bak-") }) .collect(); @@ -285,7 +285,7 @@ original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac86 #[test] fn update_does_not_duplicate_same_package_version() { let temp_dir = TempDir::new().unwrap(); - let history_path = temp_dir.path().join("Move.package-history.json"); + let history_path = temp_dir.path().join("../../Move.history.json"); let move_lock_path = temp_dir.path().join("Move.lock"); fs::write(&history_path, create_test_package_history()).unwrap(); diff --git a/product_common/src/package_registry.rs b/product_common/src/package_registry.rs index e24da84..fa5e9ad 100644 --- a/product_common/src/package_registry.rs +++ b/product_common/src/package_registry.rs @@ -94,21 +94,21 @@ impl PackageRegistry { self.envs.extend(other.envs); } - /// Creates a [PackageRegistry] from a Move.package-history.json file. + /// Creates a [PackageRegistry] from a Move.history.json file. pub fn from_package_history_json_str(package_history: &str) -> anyhow::Result { let package_history: Value = serde_json::from_str(package_history)?; let ret_val = package_history .get("aliases") - .context("invalid Move.package-history.json file: missing `aliases` object")? + .context("invalid Move.history.json file: missing `aliases` object")? .as_object() - .context("invalid Move.package-history.json file: `aliases` is not a JSON object literal")? + .context("invalid Move.history.json file: `aliases` is not a JSON object literal")? .into_iter() .try_fold(Self::default(), |mut registry, (alias, chain_id)| { let chain_id: String = chain_id .as_str() .context(format!( - "invalid Move.package-history.json file: invalid `chain-id` '{chain_id}' for alias {alias}" + "invalid Move.history.json file: invalid `chain-id` '{chain_id}' for alias {alias}" ))? .to_string(); registry.aliases.insert(alias.clone(), chain_id); @@ -117,19 +117,19 @@ impl PackageRegistry { package_history .get("envs") - .context("invalid Move.package-history.json file: missing `envs` object")? + .context("invalid Move.history.json file: missing `envs` object")? .as_object() - .context("invalid Move.package-history.json file: `envs` is not a JSON object literal")? + .context("invalid Move.history.json file: `envs` is not a JSON object literal")? .into_iter() .try_fold(ret_val, |mut registry, (chain_id, versions)| { let versions: Vec = versions .as_array() - .context(format!("invalid Move.package-history.json file: invalid versions for {chain_id}. versions is not an array"))? + .context(format!("invalid Move.history.json file: invalid versions for {chain_id}. versions is not an array"))? .iter() .try_fold(Vec::::new(), |mut arr, v| { let obj_id = ObjectID::from_hex_literal( v.as_str() - .context(format!("invalid Move.package-history.json file: invalid versions array element for {chain_id}. Elements need to be strings"))? + .context(format!("invalid Move.history.json file: invalid versions array element for {chain_id}. Elements need to be strings"))? )?; arr.push(obj_id); Ok::, anyhow::Error>(arr) From 30abbccc14a2eebbee589c6e4494753557a6c015 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Fri, 8 Aug 2025 17:26:47 +0200 Subject: [PATCH 07/28] product_common now provides the functionality, previously implemented in package_history_cli/src/main.rs to be used by build.rs scripts of product repositories Crate package_history_cli has been removed --- Cargo.toml | 2 +- package_history_cli/Cargo.toml | 18 -- package_history_cli/src/main.rs | 303 --------------------- product_common/Cargo.toml | 5 +- product_common/src/lib.rs | 2 + product_common/src/move_history_manager.rs | 262 ++++++++++++++++++ product_common/src/package_registry.rs | 60 ---- 7 files changed, 268 insertions(+), 384 deletions(-) delete mode 100644 package_history_cli/Cargo.toml delete mode 100644 package_history_cli/src/main.rs create mode 100644 product_common/src/move_history_manager.rs diff --git a/Cargo.toml b/Cargo.toml index 6d24d33..1671205 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ members = [ "iota_interaction", "bindings/wasm/iota_interaction_ts", "iota_interaction_rust", - "package_history_cli", ] [workspace.dependencies] @@ -30,6 +29,7 @@ serde_json = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } thiserror = { version = "1.0", default-features = false } tokio = { version = "1.44.2", default-features = false, features = ["process"] } +tempfile = "3.20.0" toml = "0.8" [workspace.lints.clippy] diff --git a/package_history_cli/Cargo.toml b/package_history_cli/Cargo.toml deleted file mode 100644 index a34e8f0..0000000 --- a/package_history_cli/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "package_history_cli" -version = "0.1.0" -edition = "2021" - -[[bin]] -name = "package-history" -path = "src/main.rs" - -[dependencies] -anyhow = "1.0" -chrono = { version = "0.4", features = ["serde"] } -clap = { version = "4.0", features = ["derive"] } -product_common = { path = "../product_common", features = ["package-reg-move-lock"] } -serde_json.workspace = true - -[dev-dependencies] -tempfile = "3.0" diff --git a/package_history_cli/src/main.rs b/package_history_cli/src/main.rs deleted file mode 100644 index 6c5fbb1..0000000 --- a/package_history_cli/src/main.rs +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright 2020-2025 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::fs; -use std::path::{Path, PathBuf}; - -use anyhow::{Context, Result}; -use chrono::Utc; -use clap::{Parser, Subcommand}; -use product_common::package_registry::PackageRegistry; - -#[derive(Parser)] -#[command(name = "package-history")] -#[command(about = "A CLI tool for managing Move package history files")] -#[command(version = "1.0")] -struct Cli { - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand)] -enum Commands { - /// Create an initial Move.history.json file from a Move.lock file - Init { - /// Path to the Move.lock file - #[arg(short, long, default_value = "Move.lock")] - move_lock: PathBuf, - /// Output path for the Move.history.json file - #[arg(short, long, default_value = "Move.history.json")] - output: PathBuf, - }, - /// Add a new package version to an existing Move.history.json file - Update { - /// Path to the existing Move.history.json file - #[arg(short('f'), long, default_value = "Move.history.json")] - history_file: PathBuf, - /// Path to the Move.lock file containing the new version - #[arg(short, long)] - move_lock: Option, - }, -} - -fn main() -> Result<()> { - let cli = Cli::parse(); - - match cli.command { - Commands::Init { move_lock, output } => { - init_package_history(&move_lock, &output)?; - } - Commands::Update { - history_file, - move_lock, - } => { - let move_lock_path = - move_lock.unwrap_or_else(|| history_file.parent().unwrap_or(Path::new(".")).join("Move.lock")); - update_package_history(&history_file, &move_lock_path)?; - } - } - - Ok(()) -} - -/// Creates an initial Move.history.json file from a Move.lock file -fn init_package_history(move_lock_path: &Path, output_path: &Path) -> Result<()> { - let move_lock_content = fs::read_to_string(move_lock_path) - .with_context(|| format!("Failed to read Move.lock file: {}", move_lock_path.display()))?; - - let registry = - PackageRegistry::from_move_lock_content(&move_lock_content).context("Failed to parse Move.lock file")?; - - let json_content = to_prettified_string(®istry)?; - - fs::write(output_path, json_content) - .with_context(|| format!("Failed to write to output file: {}", output_path.display()))?; - - println!( - "Successfully created Move.history.json from {}", - move_lock_path.display() - ); - - Ok(()) -} - -fn to_prettified_string(registry: &PackageRegistry) -> Result { - let json_value = serde_json::to_value(registry).context("Failed to serialize PackageRegistry to JSON value")?; - Ok(format!("{json_value:#}")) -} - -/// Updates an existing Move.history.json file with new package versions from a Move.lock file -fn update_package_history(history_file_path: &Path, move_lock_path: &Path) -> Result<()> { - // Read and deserialize existing package history - let history_content = fs::read_to_string(history_file_path).with_context(|| { - format!( - "Failed to read Move.history.json file: {}", - history_file_path.display() - ) - })?; - - let mut registry = PackageRegistry::from_package_history_json_str(&history_content) - .context("Failed to parse existing Move.history.json file")?; - - // Create backup file - create_backup_file(history_file_path)?; - - // Read and parse Move.lock file - let move_lock_content = fs::read_to_string(move_lock_path) - .with_context(|| format!("Failed to read Move.lock file: {}", move_lock_path.display()))?; - - let new_registry = - PackageRegistry::from_move_lock_content(&move_lock_content).context("Failed to parse Move.lock file")?; - - // Add new package versions from Move.lock to existing registry - for (chain_id, versions) in new_registry.envs().iter() { - if let Some(latest_version) = versions.last() { - registry.insert_new_package_version(chain_id, *latest_version); - } - } - - // Serialize and write updated registry - let updated_json_content = to_prettified_string(®istry)?; - - fs::write(history_file_path, updated_json_content) - .with_context(|| format!("Failed to write updated content to: {}", history_file_path.display()))?; - - println!( - "Successfully updated {} with new versions from {}", - history_file_path.display(), - move_lock_path.display() - ); - - Ok(()) -} - -/// Creates a backup file with timestamp -fn create_backup_file(original_path: &Path) -> Result<()> { - let timestamp = Utc::now().format("%Y%m%d_%H%M%S").to_string(); - let backup_path = original_path.with_extension(format!("json.bak-{timestamp}")); - - fs::copy(original_path, &backup_path) - .with_context(|| format!("Failed to create backup file: {}", backup_path.display()))?; - - println!("Created backup file: {}", backup_path.display()); - Ok(()) -} - -#[cfg(test)] -mod tests { - use std::fs; - - use tempfile::TempDir; - - use super::*; - - fn create_test_move_lock() -> String { - r#" -[env.mainnet] -chain-id = "6364aad5" -original-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" -latest-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" - -[env.testnet] -chain-id = "2304aa97" -original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555" -latest-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555" -"# - .to_string() - } - - fn create_test_package_history() -> String { - r#" -{ - "aliases": { - "testnet": "2304aa97", - "mainnet": "6364aad5" - }, - "envs": { - "6364aad5": ["0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"], - "2304aa97": ["0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555"] - } -} -"# - .to_string() - } - - #[test] - fn init_creates_package_history_from_move_lock() { - let temp_dir = TempDir::new().unwrap(); - let move_lock_path = temp_dir.path().join("Move.lock"); - let output_path = temp_dir.path().join("../../Move.history.json"); - - fs::write(&move_lock_path, create_test_move_lock()).unwrap(); - - init_package_history(&move_lock_path, &output_path).unwrap(); - - assert!(output_path.exists()); - let content = fs::read_to_string(&output_path).unwrap(); - assert!(content.contains("\"aliases\": {")); - assert!(content.contains("\"mainnet\": \"6364aad5\"")); - assert!(content.contains("\"testnet\": \"2304aa97\"")); - - assert!(content.contains("\"envs\": {")); - assert!(content.contains("\"2304aa97\": [")); - assert!(content.contains("\"0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555\"")); - assert!(content.contains("\"6364aad5\": [")); - assert!(content.contains("\"0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08\"")); - } - - #[test] - fn init_fails_with_nonexistent_move_lock() { - let temp_dir = TempDir::new().unwrap(); - let move_lock_path = temp_dir.path().join("nonexistent.lock"); - let output_path = temp_dir.path().join("output.json"); - - let result = init_package_history(&move_lock_path, &output_path); - assert!(result.is_err()); - } - - #[test] - fn update_adds_new_package_versions() { - let temp_dir = TempDir::new().unwrap(); - let history_path = temp_dir.path().join("../../Move.history.json"); - let move_lock_path = temp_dir.path().join("Move.lock"); - - fs::write(&history_path, create_test_package_history()).unwrap(); - - let updated_move_lock = r#" -[env.mainnet] -chain-id = "6364aad5" -latest-published-id = "0x94cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de09" -original-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" - -[env.testnet] -chain-id = "2304aa97" -latest-published-id = "0x332741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc666" -original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555" -"#; - fs::write(&move_lock_path, updated_move_lock).unwrap(); - - update_package_history(&history_path, &move_lock_path).unwrap(); - - let updated_content = fs::read_to_string(&history_path).unwrap(); - let registry = PackageRegistry::from_package_history_json_str(&updated_content).unwrap(); - - assert_eq!(registry.history("6364aad5").unwrap().len(), 2); - assert_eq!(registry.history("2304aa97").unwrap().len(), 2); - } - - #[test] - fn update_creates_backup_file() { - let temp_dir = TempDir::new().unwrap(); - let history_path = temp_dir.path().join("../../Move.history.json"); - let move_lock_path = temp_dir.path().join("Move.lock"); - - fs::write(&history_path, create_test_package_history()).unwrap(); - fs::write(&move_lock_path, create_test_move_lock()).unwrap(); - - update_package_history(&history_path, &move_lock_path).unwrap(); - - let backup_files: Vec<_> = fs::read_dir(temp_dir.path()) - .unwrap() - .filter_map(|entry| entry.ok()) - .filter(|entry| { - entry - .file_name() - .to_string_lossy() - .starts_with("Move.history.json.bak-") - }) - .collect(); - - assert_eq!(backup_files.len(), 1); - } - - #[test] - fn update_fails_with_nonexistent_history_file() { - let temp_dir = TempDir::new().unwrap(); - let history_path = temp_dir.path().join("nonexistent.json"); - let move_lock_path = temp_dir.path().join("Move.lock"); - - fs::write(&move_lock_path, create_test_move_lock()).unwrap(); - - let result = update_package_history(&history_path, &move_lock_path); - assert!(result.is_err()); - } - - #[test] - fn update_does_not_duplicate_same_package_version() { - let temp_dir = TempDir::new().unwrap(); - let history_path = temp_dir.path().join("../../Move.history.json"); - let move_lock_path = temp_dir.path().join("Move.lock"); - - fs::write(&history_path, create_test_package_history()).unwrap(); - fs::write(&move_lock_path, create_test_move_lock()).unwrap(); - - update_package_history(&history_path, &move_lock_path).unwrap(); - - let updated_content = fs::read_to_string(&history_path).unwrap(); - let registry = PackageRegistry::from_package_history_json_str(&updated_content).unwrap(); - - // Should still have only 1 version each since we're adding the same versions - assert_eq!(registry.history("6364aad5").unwrap().len(), 1); - assert_eq!(registry.history("2304aa97").unwrap().len(), 1); - } -} diff --git a/product_common/Cargo.toml b/product_common/Cargo.toml index 086a9d9..2c1f49e 100644 --- a/product_common/Cargo.toml +++ b/product_common/Cargo.toml @@ -53,6 +53,7 @@ js-sys = { version = "0.3", optional = true } [dev-dependencies] iota_interaction = { version = "0.7.0", path = "../iota_interaction" } iota_interaction_rust = { version = "0.7.0", path = "../iota_interaction_rust" } +tempfile.workspace = true [features] default = [] @@ -88,8 +89,8 @@ gas-station = ["transaction", "http-client"] # instead use reqwest::Client. default-http-client = ["http-client", "dep:reqwest"] -# PackageRegistry with Move.lock reading capabilities. -package-reg-move-lock = ["dep:toml"] +# Management functions to read Move.lock files and create/update Move.history.json files +move-history-manager = ["dep:toml"] [package.metadata.docs.rs] # To build locally: diff --git a/product_common/src/lib.rs b/product_common/src/lib.rs index 7f3bb4c..92a317e 100644 --- a/product_common/src/lib.rs +++ b/product_common/src/lib.rs @@ -10,6 +10,8 @@ pub mod error; pub mod gas_station; #[cfg(feature = "http-client")] pub mod http_client; +#[cfg(feature = "move-history-manager")] +pub mod move_history_manager; pub mod network_name; pub mod object; pub mod package_registry; diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs new file mode 100644 index 0000000..e1c4e64 --- /dev/null +++ b/product_common/src/move_history_manager.rs @@ -0,0 +1,262 @@ +// Copyright 2020-2025 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::fs; +use std::path::Path; + +use anyhow::Context; + +use iota_interaction::types::base_types::ObjectID; + +use super::package_registry::{Env, PackageRegistry}; + +fn remove_first_and_last_char_from_string(value: String) -> String { + let mut chars = value.chars(); + chars.next(); + chars.next_back(); + chars.as_str().to_string() +} + +fn to_prettified_string(registry: &PackageRegistry) -> anyhow::Result { + let json_value = serde_json::to_value(registry).context("Failed to serialize PackageRegistry to JSON value")?; + Ok(format!("{json_value:#}")) +} + +impl PackageRegistry { + /// Creates a [PackageRegistry] from a Move.lock file. + pub fn from_move_lock_content(move_lock: &str) -> anyhow::Result { + let mut move_lock: toml::Table = move_lock.parse()?; + + let mut move_lock_iter = move_lock + .remove("env") + .context("invalid Move.lock file: missing `env` table")? + .as_table_mut() + .map(std::mem::take) + .context("invalid Move.lock file: `env` is not a table")? + .into_iter(); + + move_lock_iter.try_fold(Self::default(), |mut registry, (alias, table)| { + let toml::Value::Table(mut table) = table else { + anyhow::bail!("invalid Move.lock file: invalid `env` table"); + }; + let chain_id: String = table + .remove("chain-id") + .context(format!("invalid Move.lock file: missing `chain-id` for env {alias}"))? + .try_into() + .context("invalid Move.lock file: invalid `chain-id`")?; + + let original_published_id: String = remove_first_and_last_char_from_string( + table + .get("original-published-id") + .context(format!( + "invalid Move.lock file: missing `original-published-id` for env {alias}" + ))? + .to_string(), + ); + let latest_published_id: String = remove_first_and_last_char_from_string( + table + .get("latest-published-id") + .context(format!( + "invalid Move.lock file: missing `latest-published-id` for env {alias}" + ))? + .to_string(), + ); + + let mut metadata = vec![ObjectID::from_hex_literal(original_published_id.as_str())?]; + if original_published_id != latest_published_id { + metadata.push(ObjectID::from_hex_literal(latest_published_id.as_str())?); + } + + let env = Env::new_with_alias(chain_id, alias.clone()); + registry.insert_env(env, metadata); + + Ok(registry) + }) + } +} + +pub struct MoveHistoryManager {} + +impl MoveHistoryManager { + /// Creates an initial Move.history.json file from a Move.lock file + pub fn init(move_lock_path: &Path, output_path: &Path) -> anyhow::Result<()> { + let move_lock_content = fs::read_to_string(move_lock_path) + .with_context(|| format!("Failed to read Move.lock file: {}", move_lock_path.display()))?; + + let registry = + PackageRegistry::from_move_lock_content(&move_lock_content).context("Failed to parse Move.lock file")?; + + let json_content = to_prettified_string(®istry)?; + + fs::write(output_path, json_content) + .with_context(|| format!("Failed to write to output file: {}", output_path.display()))?; + + Ok(()) + } + + /// Updates an existing Move.history.json file with new package versions from a Move.lock file + pub fn update(history_file_path: &Path, move_lock_path: &Path) -> anyhow::Result<()> { + // Read and deserialize existing package history + let history_content = fs::read_to_string(history_file_path) + .with_context(|| format!("Failed to read Move.history.json file: {}", history_file_path.display()))?; + + let mut registry = PackageRegistry::from_package_history_json_str(&history_content) + .context("Failed to parse existing Move.history.json file")?; + + // Read and parse Move.lock file + let move_lock_content = fs::read_to_string(move_lock_path) + .with_context(|| format!("Failed to read Move.lock file: {}", move_lock_path.display()))?; + + let new_registry = + PackageRegistry::from_move_lock_content(&move_lock_content).context("Failed to parse Move.lock file")?; + + // Add new package versions from Move.lock to existing registry + for (chain_id, versions) in new_registry.envs().iter() { + if let Some(latest_version) = versions.last() { + registry.insert_new_package_version(chain_id, *latest_version); + } + } + + // Serialize and write updated registry + let updated_json_content = to_prettified_string(®istry)?; + + fs::write(history_file_path, updated_json_content) + .with_context(|| format!("Failed to write updated content to: {}", history_file_path.display()))?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::fs; + + use tempfile::TempDir; + + use super::*; + + fn create_test_move_lock() -> String { + r#" +[env.mainnet] +chain-id = "6364aad5" +original-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" +latest-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" + +[env.testnet] +chain-id = "2304aa97" +original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555" +latest-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555" +"# + .to_string() + } + + fn create_test_package_history() -> String { + r#" +{ + "aliases": { + "testnet": "2304aa97", + "mainnet": "6364aad5" + }, + "envs": { + "6364aad5": ["0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"], + "2304aa97": ["0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555"] + } +} +"# + .to_string() + } + + #[test] + fn init_creates_package_history_from_move_lock() { + let temp_dir = TempDir::new().unwrap(); + let move_lock_path = temp_dir.path().join("Move.lock"); + let output_path = temp_dir.path().join("Move.history.json"); + + fs::write(&move_lock_path, create_test_move_lock()).unwrap(); + + MoveHistoryManager::init(&move_lock_path, &output_path).unwrap(); + + assert!(output_path.exists()); + let content = fs::read_to_string(&output_path).unwrap(); + assert!(content.contains("\"aliases\": {")); + assert!(content.contains("\"mainnet\": \"6364aad5\"")); + assert!(content.contains("\"testnet\": \"2304aa97\"")); + + assert!(content.contains("\"envs\": {")); + assert!(content.contains("\"2304aa97\": [")); + assert!(content.contains("\"0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555\"")); + assert!(content.contains("\"6364aad5\": [")); + assert!(content.contains("\"0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08\"")); + } + + #[test] + fn init_fails_with_nonexistent_move_lock() { + let temp_dir = TempDir::new().unwrap(); + let move_lock_path = temp_dir.path().join("nonexistent.lock"); + let output_path = temp_dir.path().join("output.json"); + + let result = MoveHistoryManager::init(&move_lock_path, &output_path); + assert!(result.is_err()); + } + + #[test] + fn update_adds_new_package_versions() { + let temp_dir = TempDir::new().unwrap(); + let history_path = temp_dir.path().join("Move.history.json"); + let move_lock_path = temp_dir.path().join("Move.lock"); + + fs::write(&history_path, create_test_package_history()).unwrap(); + + let updated_move_lock = r#" +[env.mainnet] +chain-id = "6364aad5" +latest-published-id = "0x94cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de09" +original-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" + +[env.testnet] +chain-id = "2304aa97" +latest-published-id = "0x332741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc666" +original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555" +"#; + fs::write(&move_lock_path, updated_move_lock).unwrap(); + + MoveHistoryManager::update(&history_path, &move_lock_path).unwrap(); + + let updated_content = fs::read_to_string(&history_path).unwrap(); + let registry = PackageRegistry::from_package_history_json_str(&updated_content).unwrap(); + + assert_eq!(registry.history("6364aad5").unwrap().len(), 2); + assert_eq!(registry.history("2304aa97").unwrap().len(), 2); + } + + #[test] + fn update_fails_with_nonexistent_history_file() { + let temp_dir = TempDir::new().unwrap(); + let history_path = temp_dir.path().join("nonexistent.json"); + let move_lock_path = temp_dir.path().join("Move.lock"); + + fs::write(&move_lock_path, create_test_move_lock()).unwrap(); + + let result = MoveHistoryManager::update(&history_path, &move_lock_path); + assert!(result.is_err()); + } + + #[test] + fn update_does_not_duplicate_same_package_version() { + let temp_dir = TempDir::new().unwrap(); + let history_path = temp_dir.path().join("Move.history.json"); + let move_lock_path = temp_dir.path().join("Move.lock"); + + fs::write(&history_path, create_test_package_history()).unwrap(); + fs::write(&move_lock_path, create_test_move_lock()).unwrap(); + + MoveHistoryManager::update(&history_path, &move_lock_path).unwrap(); + + let updated_content = fs::read_to_string(&history_path).unwrap(); + let registry = PackageRegistry::from_package_history_json_str(&updated_content).unwrap(); + + // Should still have only 1 version each since we're adding the same versions + assert_eq!(registry.history("6364aad5").unwrap().len(), 1); + assert_eq!(registry.history("2304aa97").unwrap().len(), 1); + } +} diff --git a/product_common/src/package_registry.rs b/product_common/src/package_registry.rs index fa5e9ad..16cdf1d 100644 --- a/product_common/src/package_registry.rs +++ b/product_common/src/package_registry.rs @@ -138,66 +138,6 @@ impl PackageRegistry { Ok(registry) }) } - - #[cfg(feature = "package-reg-move-lock")] - /// Creates a [PackageRegistry] from a Move.lock file. - pub fn from_move_lock_content(move_lock: &str) -> anyhow::Result { - let mut move_lock: toml::Table = move_lock.parse()?; - - let mut move_lock_iter = move_lock - .remove("env") - .context("invalid Move.lock file: missing `env` table")? - .as_table_mut() - .map(std::mem::take) - .context("invalid Move.lock file: `env` is not a table")? - .into_iter(); - - move_lock_iter.try_fold(Self::default(), |mut registry, (alias, table)| { - let toml::Value::Table(mut table) = table else { - anyhow::bail!("invalid Move.lock file: invalid `env` table"); - }; - let chain_id: String = table - .remove("chain-id") - .context(format!("invalid Move.lock file: missing `chain-id` for env {alias}"))? - .try_into() - .context("invalid Move.lock file: invalid `chain-id`")?; - - let original_published_id: String = remove_first_and_last_char_from_string( - table - .get("original-published-id") - .context(format!( - "invalid Move.lock file: missing `original-published-id` for env {alias}" - ))? - .to_string(), - ); - let latest_published_id: String = remove_first_and_last_char_from_string( - table - .get("latest-published-id") - .context(format!( - "invalid Move.lock file: missing `latest-published-id` for env {alias}" - ))? - .to_string(), - ); - - let mut metadata = vec![ObjectID::from_hex_literal(original_published_id.as_str())?]; - if original_published_id != latest_published_id { - metadata.push(ObjectID::from_hex_literal(latest_published_id.as_str())?); - } - - let env = Env::new_with_alias(chain_id, alias.clone()); - registry.insert_env(env, metadata); - - Ok(registry) - }) - } -} - -#[cfg(feature = "package-reg-move-lock")] -fn remove_first_and_last_char_from_string(value: String) -> String { - let mut chars = value.chars(); - chars.next(); - chars.next_back(); - chars.as_str().to_string() } #[cfg(test)] From 29ca4a62f44831029c4c49601e0d244d9fb27897 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Fri, 8 Aug 2025 19:42:09 +0200 Subject: [PATCH 08/28] MoveHistoryManager holds move_lock_path and history_file_path now and can ignore envs specified by their alias --- product_common/src/move_history_manager.rs | 163 ++++++++++++++++++--- product_common/src/package_registry.rs | 10 ++ 2 files changed, 149 insertions(+), 24 deletions(-) diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs index e1c4e64..306a387 100644 --- a/product_common/src/move_history_manager.rs +++ b/product_common/src/move_history_manager.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; use anyhow::Context; @@ -23,7 +23,16 @@ fn to_prettified_string(registry: &PackageRegistry) -> anyhow::Result { } impl PackageRegistry { - /// Creates a [PackageRegistry] from a Move.lock file. + /// Creates a [`PackageRegistry`] from the content of a `Move.lock` file. + /// + /// # Arguments + /// * `move_lock` - A string containing the content of the `Move.lock` file. + /// + /// # Returns + /// A `PackageRegistry` instance populated with data from the `Move.lock` file. + /// + /// # Errors + /// Returns an error if the `Move.lock` file content is invalid or cannot be parsed. pub fn from_move_lock_content(move_lock: &str) -> anyhow::Result { let mut move_lock: toml::Table = move_lock.parse()?; @@ -75,41 +84,98 @@ impl PackageRegistry { } } -pub struct MoveHistoryManager {} +/// Manages the history of Move packages, including initialization and updates. +pub struct MoveHistoryManager { + move_lock_path: PathBuf, + history_file_path: PathBuf, + aliases_to_ignore: Vec, +} impl MoveHistoryManager { + /// Creates a new `MoveHistoryManager` instance. + /// + /// # Arguments + /// * `move_lock_path` - Path to the `Move.lock` file. + /// * `history_file_path` - Path to the `M̀ove.history.toml` file. + /// * `aliases_to_ignore` - Optional list of environment aliases to ignore. + /// If `aliases_to_ignore` is not provided, it defaults to `["localnet"]`. + /// + /// # Returns + /// A new `MoveHistoryManager` instance. + /// + /// Doesn't check if any of the provided paths are invalid or if the `Move.lock` file cannot be parsed. + /// Functions `init` and `update` will handle those checks. + pub fn new(move_lock_path: &Path, history_file_path: &Path, aliases_to_ignore: Option>) -> Self { + let aliases_to_ignore = aliases_to_ignore + .unwrap_or(vec!["localnet".to_string()]); + Self { + move_lock_path: move_lock_path.to_owned(), + history_file_path: history_file_path.to_owned(), + aliases_to_ignore, + } + } + + /// Checks if the Move.history.json file exists. + pub fn history_file_exists(&self) -> bool { + self.history_file_path.exists() && self.history_file_path.is_file() + } + + /// Returns the list of environment aliases to ignore. + pub fn aliases_to_ignore(&self) -> &[String] { + &self.aliases_to_ignore + } + + /// Returns the path to the Move.lock file. + pub fn move_lock_path(&self) -> &Path { + &self.move_lock_path + } + + /// Returns the path to the Move.history.json file. + pub fn history_file_path(&self) -> &Path { + &self.history_file_path + } + /// Creates an initial Move.history.json file from a Move.lock file - pub fn init(move_lock_path: &Path, output_path: &Path) -> anyhow::Result<()> { - let move_lock_content = fs::read_to_string(move_lock_path) - .with_context(|| format!("Failed to read Move.lock file: {}", move_lock_path.display()))?; + /// Will ignore any environment aliases specified in `aliases_to_ignore`. + pub fn init(&self) -> anyhow::Result<()> { + let move_lock_content = fs::read_to_string(&self.move_lock_path) + .with_context(|| format!("Failed to read Move.lock file: {}", &self.move_lock_path.display()))?; - let registry = + let mut registry = PackageRegistry::from_move_lock_content(&move_lock_content).context("Failed to parse Move.lock file")?; + for alias in self.aliases_to_ignore.iter() { + let _ = registry.remove_env_by_alias(alias); + } + let json_content = to_prettified_string(®istry)?; - fs::write(output_path, json_content) - .with_context(|| format!("Failed to write to output file: {}", output_path.display()))?; + fs::write(&self.history_file_path, json_content) + .with_context(|| format!("Failed to write to output file: {}", self.history_file_path.display()))?; Ok(()) } /// Updates an existing Move.history.json file with new package versions from a Move.lock file - pub fn update(history_file_path: &Path, move_lock_path: &Path) -> anyhow::Result<()> { + pub fn update(&self) -> anyhow::Result<()> { // Read and deserialize existing package history - let history_content = fs::read_to_string(history_file_path) - .with_context(|| format!("Failed to read Move.history.json file: {}", history_file_path.display()))?; + let history_content = fs::read_to_string(&self.history_file_path) + .with_context(|| format!("Failed to read Move.history.json file: {}", self.history_file_path.display()))?; let mut registry = PackageRegistry::from_package_history_json_str(&history_content) .context("Failed to parse existing Move.history.json file")?; // Read and parse Move.lock file - let move_lock_content = fs::read_to_string(move_lock_path) - .with_context(|| format!("Failed to read Move.lock file: {}", move_lock_path.display()))?; + let move_lock_content = fs::read_to_string(&self.move_lock_path) + .with_context(|| format!("Failed to read Move.lock file: {}", self.move_lock_path.display()))?; - let new_registry = + let mut new_registry = PackageRegistry::from_move_lock_content(&move_lock_content).context("Failed to parse Move.lock file")?; + for alias in self.aliases_to_ignore.iter() { + let _ = new_registry.remove_env_by_alias(alias); + } + // Add new package versions from Move.lock to existing registry for (chain_id, versions) in new_registry.envs().iter() { if let Some(latest_version) = versions.last() { @@ -120,8 +186,8 @@ impl MoveHistoryManager { // Serialize and write updated registry let updated_json_content = to_prettified_string(®istry)?; - fs::write(history_file_path, updated_json_content) - .with_context(|| format!("Failed to write updated content to: {}", history_file_path.display()))?; + fs::write(&self.history_file_path, updated_json_content) + .with_context(|| format!("Failed to write updated content to: {}", self.history_file_path.display()))?; Ok(()) } @@ -146,6 +212,11 @@ latest-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054 chain-id = "2304aa97" original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555" latest-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555" + +[env.localnet] +chain-id = "ecc0606a" +original-published-id = "0xfbddb4631d027b2c4f0b4b90c020713d258ed32bdb342b5397f4da71edb7478b" +latest-published-id = "0xfbddb4631d027b2c4f0b4b90c020713d258ed32bdb342b5397f4da71edb7478b" "# .to_string() } @@ -174,19 +245,22 @@ latest-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864a fs::write(&move_lock_path, create_test_move_lock()).unwrap(); - MoveHistoryManager::init(&move_lock_path, &output_path).unwrap(); + MoveHistoryManager::new(&move_lock_path, &output_path, None).init().unwrap(); assert!(output_path.exists()); let content = fs::read_to_string(&output_path).unwrap(); assert!(content.contains("\"aliases\": {")); assert!(content.contains("\"mainnet\": \"6364aad5\"")); assert!(content.contains("\"testnet\": \"2304aa97\"")); + assert!(!content.contains("\"localnet\": \"ecc0606a\"")); assert!(content.contains("\"envs\": {")); assert!(content.contains("\"2304aa97\": [")); assert!(content.contains("\"0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555\"")); assert!(content.contains("\"6364aad5\": [")); assert!(content.contains("\"0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08\"")); + assert!(!content.contains("\"ecc0606a\": [")); + assert!(!content.contains("\"0xfbddb4631d027b2c4f0b4b90c020713d258ed32bdb342b5397f4da71edb7478b\"")); } #[test] @@ -195,7 +269,7 @@ latest-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864a let move_lock_path = temp_dir.path().join("nonexistent.lock"); let output_path = temp_dir.path().join("output.json"); - let result = MoveHistoryManager::init(&move_lock_path, &output_path); + let result = MoveHistoryManager::new(&move_lock_path, &output_path, None).init(); assert!(result.is_err()); } @@ -217,16 +291,22 @@ original-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed20 chain-id = "2304aa97" latest-published-id = "0x332741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc666" original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555" + +[env.localnet] +chain-id = "ecc0606a" +original-published-id = "0xfbddb4631d027b2c4f0b4b90c020713d258ed32bdb342b5397f4da71edb7478b" +latest-published-id = "0x0d88bcecde97585d50207a029a85d7ea0bacf73ab741cbaa975a6e279251033a" "#; fs::write(&move_lock_path, updated_move_lock).unwrap(); - MoveHistoryManager::update(&history_path, &move_lock_path).unwrap(); + MoveHistoryManager::new(&move_lock_path, &history_path, None).update().unwrap(); let updated_content = fs::read_to_string(&history_path).unwrap(); let registry = PackageRegistry::from_package_history_json_str(&updated_content).unwrap(); assert_eq!(registry.history("6364aad5").unwrap().len(), 2); assert_eq!(registry.history("2304aa97").unwrap().len(), 2); + assert_eq!(registry.history("ecc0606a"), None); } #[test] @@ -237,7 +317,7 @@ original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac86 fs::write(&move_lock_path, create_test_move_lock()).unwrap(); - let result = MoveHistoryManager::update(&history_path, &move_lock_path); + let result = MoveHistoryManager::new(&move_lock_path, &history_path, None).update(); assert!(result.is_err()); } @@ -249,8 +329,7 @@ original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac86 fs::write(&history_path, create_test_package_history()).unwrap(); fs::write(&move_lock_path, create_test_move_lock()).unwrap(); - - MoveHistoryManager::update(&history_path, &move_lock_path).unwrap(); + MoveHistoryManager::new(&move_lock_path, &history_path, None).update().unwrap(); let updated_content = fs::read_to_string(&history_path).unwrap(); let registry = PackageRegistry::from_package_history_json_str(&updated_content).unwrap(); @@ -259,4 +338,40 @@ original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac86 assert_eq!(registry.history("6364aad5").unwrap().len(), 1); assert_eq!(registry.history("2304aa97").unwrap().len(), 1); } -} + + #[test] + fn history_file_exists_returns_true_when_file_exists() { + let temp_dir = TempDir::new().unwrap(); + let move_lock_path = temp_dir.path().join("Move.lock"); + let history_path = temp_dir.path().join("Move.history.json"); + + // Create the history file + fs::write(&history_path, "{}").unwrap(); + + let manager = MoveHistoryManager::new(&move_lock_path, &history_path, None); + assert!(manager.history_file_exists()); + } + + #[test] + fn history_file_exists_returns_false_when_file_does_not_exist() { + let temp_dir = TempDir::new().unwrap(); + let move_lock_path = temp_dir.path().join("Move.lock"); + let history_path = temp_dir.path().join("nonexistent.json"); + + let manager = MoveHistoryManager::new(&move_lock_path, &history_path, None); + assert!(!manager.history_file_exists()); + } + + #[test] + fn history_file_exists_returns_false_when_path_is_directory() { + let temp_dir = TempDir::new().unwrap(); + let move_lock_path = temp_dir.path().join("Move.lock"); + let history_path = temp_dir.path().join("directory"); + + // Create a directory instead of a file + fs::create_dir(&history_path).unwrap(); + + let manager = MoveHistoryManager::new(&move_lock_path, &history_path, None); + assert!(!manager.history_file_exists()); + } +} \ No newline at end of file diff --git a/product_common/src/package_registry.rs b/product_common/src/package_registry.rs index 16cdf1d..b4058e8 100644 --- a/product_common/src/package_registry.rs +++ b/product_common/src/package_registry.rs @@ -64,6 +64,16 @@ impl PackageRegistry { .iter() .find_map(|(alias, chain)| (chain == chain_id).then_some(alias.as_str())) } + + /// Removes the environment specified by the alias from the registry. + /// Returns the removed environment's versions if it existed, or `None` if the alias was not found. + pub fn remove_env_by_alias(&mut self, alias: &str) -> Option> { + if let Some(chain_id) = self.aliases.remove(alias) { + self.envs.remove(&chain_id) + } else { + None + } + } /// Returns the envs of this package registry. pub fn envs(&self) -> &HashMap> { From 4c61d8d2f9182c1c9ceb19d26b7ce4250c19cbc1 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 11 Aug 2025 09:53:46 +0200 Subject: [PATCH 09/28] Add function move_lock_file_exists() --- product_common/src/move_history_manager.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs index 306a387..860664f 100644 --- a/product_common/src/move_history_manager.rs +++ b/product_common/src/move_history_manager.rs @@ -120,6 +120,11 @@ impl MoveHistoryManager { self.history_file_path.exists() && self.history_file_path.is_file() } + /// Checks if the Move.lock file exists. + pub fn move_lock_file_exists(&self) -> bool { + self.move_lock_path.exists() && self.move_lock_path.is_file() + } + /// Returns the list of environment aliases to ignore. pub fn aliases_to_ignore(&self) -> &[String] { &self.aliases_to_ignore From 39aa89856f06406f329d708c1de7074f6d60e897 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 11 Aug 2025 12:02:47 +0200 Subject: [PATCH 10/28] Add function manage_history_file() providing main functionality needed in build.rs implementations --- product_common/src/move_history_manager.rs | 235 ++++++++++++++++----- 1 file changed, 179 insertions(+), 56 deletions(-) diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs index 860664f..4a36f24 100644 --- a/product_common/src/move_history_manager.rs +++ b/product_common/src/move_history_manager.rs @@ -5,7 +5,6 @@ use std::fs; use std::path::{Path, PathBuf}; use anyhow::Context; - use iota_interaction::types::base_types::ObjectID; use super::package_registry::{Env, PackageRegistry}; @@ -97,8 +96,8 @@ impl MoveHistoryManager { /// # Arguments /// * `move_lock_path` - Path to the `Move.lock` file. /// * `history_file_path` - Path to the `M̀ove.history.toml` file. - /// * `aliases_to_ignore` - Optional list of environment aliases to ignore. - /// If `aliases_to_ignore` is not provided, it defaults to `["localnet"]`. + /// * `aliases_to_ignore` - Optional list of environment aliases to ignore. If `aliases_to_ignore` is not provided, it + /// defaults to `["localnet"]`. /// /// # Returns /// A new `MoveHistoryManager` instance. @@ -106,8 +105,7 @@ impl MoveHistoryManager { /// Doesn't check if any of the provided paths are invalid or if the `Move.lock` file cannot be parsed. /// Functions `init` and `update` will handle those checks. pub fn new(move_lock_path: &Path, history_file_path: &Path, aliases_to_ignore: Option>) -> Self { - let aliases_to_ignore = aliases_to_ignore - .unwrap_or(vec!["localnet".to_string()]); + let aliases_to_ignore = aliases_to_ignore.unwrap_or(vec!["localnet".to_string()]); Self { move_lock_path: move_lock_path.to_owned(), history_file_path: history_file_path.to_owned(), @@ -140,6 +138,56 @@ impl MoveHistoryManager { &self.history_file_path } + /// Manages the Move history file by either initializing a new one or updating an existing one + /// based on the current Move.lock file. + /// + /// This method checks for the existence of both the Move.lock and Move history files, + /// and performs the appropriate action: + /// - If Move.lock exists and the history file exists: Updates the history file + /// - If Move.lock exists but the history file doesn't: Creates a new history file + /// - If Move.lock doesn't exist: Skips any action + /// + /// Progress messages can be printed to the app console during the operation via the callback function + /// provided using the `console_out` argument. + /// + /// # Arguments + /// * `console_out` - Can be used to output status messages in the app console. It should be a closure that takes a + /// `String` as an argument. Example: `|message| { println!("{}", message); }` + /// # Returns + /// A `Result` that indicates success or contains an error if something went wrong during the process. + /// + /// # Errors + /// This method may return errors from the underlying `init()` or `update()` functions + /// if there are issues reading or writing files. + pub fn manage_history_file(&self, console_out: impl Fn(String)) -> anyhow::Result<()> { + let move_history_path = self.history_file_path.to_string_lossy(); + let move_lock_path = self.history_file_path.to_string_lossy(); + if self.move_lock_file_exists() { + if self.history_file_exists() { + // If the output file already exists, update it. + console_out(format!("File `{move_history_path}` already exists, updating...")); + self + .update() + .expect("Successfully updating `Move.history.json` file with `Move.lock` content"); + console_out(format!( + "Successfully updated`{move_history_path}` with content of `{move_lock_path}`" + )); + } else { + // If the output file does not exist, create it. + console_out(format!("File `{move_history_path}` does not exist, creating...")); + self + .init() + .expect("Successfully creating a `Move.history.json` file with `Move.lock` content"); + console_out(format!( + "Successfully created file `{move_history_path}` with content of `{move_lock_path}` content" + )); + } + } else { + console_out(format!("File `{move_history_path}` does not exist, skipping...")); + } + Ok(()) + } + /// Creates an initial Move.history.json file from a Move.lock file /// Will ignore any environment aliases specified in `aliases_to_ignore`. pub fn init(&self) -> anyhow::Result<()> { @@ -164,8 +212,12 @@ impl MoveHistoryManager { /// Updates an existing Move.history.json file with new package versions from a Move.lock file pub fn update(&self) -> anyhow::Result<()> { // Read and deserialize existing package history - let history_content = fs::read_to_string(&self.history_file_path) - .with_context(|| format!("Failed to read Move.history.json file: {}", self.history_file_path.display()))?; + let history_content = fs::read_to_string(&self.history_file_path).with_context(|| { + format!( + "Failed to read Move.history.json file: {}", + self.history_file_path.display() + ) + })?; let mut registry = PackageRegistry::from_package_history_json_str(&history_content) .context("Failed to parse existing Move.history.json file")?; @@ -191,8 +243,12 @@ impl MoveHistoryManager { // Serialize and write updated registry let updated_json_content = to_prettified_string(®istry)?; - fs::write(&self.history_file_path, updated_json_content) - .with_context(|| format!("Failed to write updated content to: {}", self.history_file_path.display()))?; + fs::write(&self.history_file_path, updated_json_content).with_context(|| { + format!( + "Failed to write updated content to: {}", + self.history_file_path.display() + ) + })?; Ok(()) } @@ -242,15 +298,100 @@ latest-published-id = "0xfbddb4631d027b2c4f0b4b90c020713d258ed32bdb342b5397f4da7 .to_string() } - #[test] - fn init_creates_package_history_from_move_lock() { + enum InitialTestFile { + None, + MoveLock, + HistoryFile, + } + + fn setup_missing_history_file_test( + history_path: &str, + move_lock_path: &str, + initial_file: InitialTestFile, + ) -> (TempDir, PathBuf, PathBuf, MoveHistoryManager) { let temp_dir = TempDir::new().unwrap(); - let move_lock_path = temp_dir.path().join("Move.lock"); - let output_path = temp_dir.path().join("Move.history.json"); + let history_path = temp_dir.path().join(history_path); + let move_lock_path = temp_dir.path().join(move_lock_path); - fs::write(&move_lock_path, create_test_move_lock()).unwrap(); + match initial_file { + InitialTestFile::None => { + // Do not create any initial files + } + InitialTestFile::MoveLock => { + fs::write(&move_lock_path, create_test_move_lock()).unwrap(); + } + InitialTestFile::HistoryFile => { + fs::write(&history_path, create_test_package_history()).unwrap(); + } + } - MoveHistoryManager::new(&move_lock_path, &output_path, None).init().unwrap(); + let history_manager = MoveHistoryManager::new(&move_lock_path, &history_path, None); + (temp_dir, history_path, move_lock_path, history_manager) + } + + #[test] + fn manage_history_file_creates_new_file_when_move_lock_exists_and_history_file_does_not() { + let (_temp_dir, history_path, _move_lock_path, history_manager) = + setup_missing_history_file_test("Move.history.json", "Move.lock", InitialTestFile::MoveLock); + + history_manager + .manage_history_file(|message| { + println!("{}", message); + }) + .unwrap(); + + assert!(history_path.exists()); + let content = fs::read_to_string(&history_path).unwrap(); + assert!(content.contains("\"aliases\": {")); + assert!(content.contains("\"mainnet\": \"6364aad5\"")); + assert!(content.contains("\"testnet\": \"2304aa97\"")); + } + + #[test] + fn manage_history_file_updates_existing_file_when_both_files_exist() { + let (_temp_dir, history_path, move_lock_path, history_manager) = + setup_missing_history_file_test("Move.history.json", "Move.lock", InitialTestFile::HistoryFile); + + let updated_move_lock = r#" +[env.mainnet] +chain-id = "6364aad5" +latest-published-id = "0x94cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de09" +original-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08" +"#; + fs::write(&move_lock_path, updated_move_lock).unwrap(); + + history_manager + .manage_history_file(|message| { + println!("{}", message); + }) + .unwrap(); + + let updated_content = fs::read_to_string(&history_path).unwrap(); + let registry = PackageRegistry::from_package_history_json_str(&updated_content).unwrap(); + + assert_eq!(registry.history("6364aad5").unwrap().len(), 2); + } + + #[test] + fn manage_history_file_skips_action_when_move_lock_does_not_exist() { + let (_temp_dir, history_path, _move_lock_path, history_manager) = + setup_missing_history_file_test("Move.history.json", "nonexistent.lock", InitialTestFile::None); + + history_manager + .manage_history_file(|message| { + println!("{}", message); + }) + .unwrap(); + + assert!(!history_path.exists()); + } + + #[test] + fn init_creates_package_history_from_move_lock() { + let (_temp_dir, output_path, _move_lock_path, history_manager) = + setup_missing_history_file_test("Move.history.json", "Move.lock", InitialTestFile::MoveLock); + + history_manager.init().unwrap(); assert!(output_path.exists()); let content = fs::read_to_string(&output_path).unwrap(); @@ -270,21 +411,17 @@ latest-published-id = "0xfbddb4631d027b2c4f0b4b90c020713d258ed32bdb342b5397f4da7 #[test] fn init_fails_with_nonexistent_move_lock() { - let temp_dir = TempDir::new().unwrap(); - let move_lock_path = temp_dir.path().join("nonexistent.lock"); - let output_path = temp_dir.path().join("output.json"); + let (_temp_dir, _history_path, _move_lock_path, history_manager) = + setup_missing_history_file_test("output.json", "nonexistent.lock", InitialTestFile::None); - let result = MoveHistoryManager::new(&move_lock_path, &output_path, None).init(); + let result = history_manager.init(); assert!(result.is_err()); } #[test] fn update_adds_new_package_versions() { - let temp_dir = TempDir::new().unwrap(); - let history_path = temp_dir.path().join("Move.history.json"); - let move_lock_path = temp_dir.path().join("Move.lock"); - - fs::write(&history_path, create_test_package_history()).unwrap(); + let (_temp_dir, history_path, move_lock_path, history_manager) = + setup_missing_history_file_test("Move.history.json", "Move.lock", InitialTestFile::HistoryFile); let updated_move_lock = r#" [env.mainnet] @@ -304,7 +441,7 @@ latest-published-id = "0x0d88bcecde97585d50207a029a85d7ea0bacf73ab741cbaa975a6e2 "#; fs::write(&move_lock_path, updated_move_lock).unwrap(); - MoveHistoryManager::new(&move_lock_path, &history_path, None).update().unwrap(); + history_manager.update().unwrap(); let updated_content = fs::read_to_string(&history_path).unwrap(); let registry = PackageRegistry::from_package_history_json_str(&updated_content).unwrap(); @@ -316,25 +453,21 @@ latest-published-id = "0x0d88bcecde97585d50207a029a85d7ea0bacf73ab741cbaa975a6e2 #[test] fn update_fails_with_nonexistent_history_file() { - let temp_dir = TempDir::new().unwrap(); - let history_path = temp_dir.path().join("nonexistent.json"); - let move_lock_path = temp_dir.path().join("Move.lock"); - - fs::write(&move_lock_path, create_test_move_lock()).unwrap(); + let (_temp_dir, _history_path, _move_lock_path, history_manager) = + setup_missing_history_file_test("nonexistent.json", "Move.lock", InitialTestFile::MoveLock); - let result = MoveHistoryManager::new(&move_lock_path, &history_path, None).update(); + let result = history_manager.update(); assert!(result.is_err()); } #[test] fn update_does_not_duplicate_same_package_version() { - let temp_dir = TempDir::new().unwrap(); - let history_path = temp_dir.path().join("Move.history.json"); - let move_lock_path = temp_dir.path().join("Move.lock"); + let (_temp_dir, history_path, move_lock_path, history_manager) = + setup_missing_history_file_test("Move.history.json", "Move.lock", InitialTestFile::HistoryFile); - fs::write(&history_path, create_test_package_history()).unwrap(); fs::write(&move_lock_path, create_test_move_lock()).unwrap(); - MoveHistoryManager::new(&move_lock_path, &history_path, None).update().unwrap(); + + history_manager.update().unwrap(); let updated_content = fs::read_to_string(&history_path).unwrap(); let registry = PackageRegistry::from_package_history_json_str(&updated_content).unwrap(); @@ -346,37 +479,27 @@ latest-published-id = "0x0d88bcecde97585d50207a029a85d7ea0bacf73ab741cbaa975a6e2 #[test] fn history_file_exists_returns_true_when_file_exists() { - let temp_dir = TempDir::new().unwrap(); - let move_lock_path = temp_dir.path().join("Move.lock"); - let history_path = temp_dir.path().join("Move.history.json"); - - // Create the history file - fs::write(&history_path, "{}").unwrap(); + let (_temp_dir, _history_path, _move_lock_path, history_manager) = + setup_missing_history_file_test("Move.history.json", "Move.lock", InitialTestFile::HistoryFile); - let manager = MoveHistoryManager::new(&move_lock_path, &history_path, None); - assert!(manager.history_file_exists()); + assert!(history_manager.history_file_exists()); } #[test] fn history_file_exists_returns_false_when_file_does_not_exist() { - let temp_dir = TempDir::new().unwrap(); - let move_lock_path = temp_dir.path().join("Move.lock"); - let history_path = temp_dir.path().join("nonexistent.json"); + let (_temp_dir, _history_path, _move_lock_path, history_manager) = + setup_missing_history_file_test("nonexistent.json", "Move.lock", InitialTestFile::None); - let manager = MoveHistoryManager::new(&move_lock_path, &history_path, None); - assert!(!manager.history_file_exists()); + assert!(!history_manager.history_file_exists()); } #[test] fn history_file_exists_returns_false_when_path_is_directory() { - let temp_dir = TempDir::new().unwrap(); - let move_lock_path = temp_dir.path().join("Move.lock"); - let history_path = temp_dir.path().join("directory"); + let (_temp_dir, history_path, _move_lock_path, history_manager) = + setup_missing_history_file_test("directory", "Move.lock", InitialTestFile::None); // Create a directory instead of a file fs::create_dir(&history_path).unwrap(); - - let manager = MoveHistoryManager::new(&move_lock_path, &history_path, None); - assert!(!manager.history_file_exists()); + assert!(!history_manager.history_file_exists()); } -} \ No newline at end of file +} From dc77438ccba917174417ce775eab2c9b18f81451 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 11 Aug 2025 12:03:14 +0200 Subject: [PATCH 11/28] fix fmt issues --- product_common/src/package_registry.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/product_common/src/package_registry.rs b/product_common/src/package_registry.rs index b4058e8..e29e1ee 100644 --- a/product_common/src/package_registry.rs +++ b/product_common/src/package_registry.rs @@ -64,7 +64,7 @@ impl PackageRegistry { .iter() .find_map(|(alias, chain)| (chain == chain_id).then_some(alias.as_str())) } - + /// Removes the environment specified by the alias from the registry. /// Returns the removed environment's versions if it existed, or `None` if the alias was not found. pub fn remove_env_by_alias(&mut self, alias: &str) -> Option> { From 26a2f7d1e2cb09bb60bf471c4dcc018e7da885a6 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 11 Aug 2025 12:17:04 +0200 Subject: [PATCH 12/28] fix warnings "hiding a lifetime that's elided elsewhere is confusing" --- iota_interaction/src/iota_verifiable_credential.rs | 2 +- iota_interaction/src/move_type.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/iota_interaction/src/iota_verifiable_credential.rs b/iota_interaction/src/iota_verifiable_credential.rs index 2ef4ff7..d25dedc 100644 --- a/iota_interaction/src/iota_verifiable_credential.rs +++ b/iota_interaction/src/iota_verifiable_credential.rs @@ -29,7 +29,7 @@ impl MoveType for IotaVerifiableCredential { TypeTag::from_str(&format!("{package}::public_vc::PublicVc")).expect("valid utf8") } - fn get_typed_value(&self, _package: ObjectID) -> TypedValue + fn get_typed_value(&self, _package: ObjectID) -> TypedValue<'_, Self> where Self: MoveType, Self: Sized, diff --git a/iota_interaction/src/move_type.rs b/iota_interaction/src/move_type.rs index 8fd8c58..4e60f99 100644 --- a/iota_interaction/src/move_type.rs +++ b/iota_interaction/src/move_type.rs @@ -17,7 +17,7 @@ pub trait MoveType: Serialize { /// Returns the Move type for this type. fn move_type(package: ObjectID) -> TypeTag; - fn get_typed_value(&self, _package: ObjectID) -> TypedValue + fn get_typed_value(&self, _package: ObjectID) -> TypedValue<'_, Self> where Self: MoveType, Self: Sized, From 01f1e11c2474f2ac3f7b60bcf5bbb456f608f0b0 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 11 Aug 2025 12:25:11 +0200 Subject: [PATCH 13/28] fix bug in `console_out` messages of fn manage_history_file() --- product_common/src/move_history_manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs index 4a36f24..d34ce24 100644 --- a/product_common/src/move_history_manager.rs +++ b/product_common/src/move_history_manager.rs @@ -161,7 +161,7 @@ impl MoveHistoryManager { /// if there are issues reading or writing files. pub fn manage_history_file(&self, console_out: impl Fn(String)) -> anyhow::Result<()> { let move_history_path = self.history_file_path.to_string_lossy(); - let move_lock_path = self.history_file_path.to_string_lossy(); + let move_lock_path = self.move_lock_path.to_string_lossy(); if self.move_lock_file_exists() { if self.history_file_exists() { // If the output file already exists, update it. From d8a6b19c65c36cd00bb4302c930b5697fd1b3aea Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 11 Aug 2025 13:19:31 +0200 Subject: [PATCH 14/28] Updated MoveHistoryManager docs --- product_common/src/move_history_manager.rs | 36 +++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs index d34ce24..1803d47 100644 --- a/product_common/src/move_history_manager.rs +++ b/product_common/src/move_history_manager.rs @@ -83,7 +83,41 @@ impl PackageRegistry { } } -/// Manages the history of Move packages, including initialization and updates. +/// Manages the content of `Move.history.json` files, including initialization and updates. +/// Provides the main functionality needed to implement a build script in IOTA product repositories. +/// +/// Libraries in IOTA product repositories, depending on Move packages provided in the same repository, +/// should have a `build.rs` script (contained in the libraries root folder), that manages the content of +/// the `Move.history.json` file that is located next to the `Move.lock` file of the Move package. +/// +/// # Example `build.rs` script +/// This example shows how to use the `MoveHistoryManager` in a build.rs` script. +/// +/// * Please replace `` with the actual name of the Move package +/// * In this example, the Move package is expected be located in a parent +/// directory of the library package. If this is not the case, please edit the file paths accordingly +/// +/// ```rust +/// use std::path::PathBuf; +/// use product_common::move_history_manager::MoveHistoryManager; +/// +/// fn main() { +/// let move_lock_path = "..//Move.lock"; +/// println!("[build.rs] move_lock_path: {move_lock_path}"); +/// let move_history_path = "..//Move.history.json"; +/// println!("[build.rs] move_history_path: {move_history_path}"); +/// +/// MoveHistoryManager::new( +/// &PathBuf::from(move_lock_path), +/// &PathBuf::from(move_history_path), +/// // Use `Some(vec![])` instead of `None`, if you don't want to ignore `localnet` +/// None, +/// ).manage_history_file(|message| { println!("[build.rs] {}", message); }) +/// .expect("Successfully managed Move history file"); +/// +/// // Tell Cargo to rerun this build script if the Move.lock file changes. +/// println!("cargo::rerun-if-changed={move_lock_path}"); +/// } pub struct MoveHistoryManager { move_lock_path: PathBuf, history_file_path: PathBuf, From 1bf1bd13d6220890828f17c1fc84fd47c61f9509 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 11 Aug 2025 13:25:12 +0200 Subject: [PATCH 15/28] fix fmt issue --- product_common/src/move_history_manager.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs index 1803d47..42c188e 100644 --- a/product_common/src/move_history_manager.rs +++ b/product_common/src/move_history_manager.rs @@ -94,8 +94,8 @@ impl PackageRegistry { /// This example shows how to use the `MoveHistoryManager` in a build.rs` script. /// /// * Please replace `` with the actual name of the Move package -/// * In this example, the Move package is expected be located in a parent -/// directory of the library package. If this is not the case, please edit the file paths accordingly +/// * In this example, the Move package is expected be located in a parent directory of the library package. If this is +/// not the case, please edit the file paths accordingly /// /// ```rust /// use std::path::PathBuf; From ca6c8caa8cb8dda9bc973a20d07cf5fb958d581f Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 11 Aug 2025 13:39:39 +0200 Subject: [PATCH 16/28] Enhance MoveHistoryManager docs --- product_common/src/move_history_manager.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs index 42c188e..56db810 100644 --- a/product_common/src/move_history_manager.rs +++ b/product_common/src/move_history_manager.rs @@ -118,6 +118,16 @@ impl PackageRegistry { /// // Tell Cargo to rerun this build script if the Move.lock file changes. /// println!("cargo::rerun-if-changed={move_lock_path}"); /// } +/// ``` +/// +/// To use the `MoveHistoryManager` in a `build.rs` script in an IOTA product repository, you need +/// to add the following build dependency in the `cargo.toml` of the crate containing the `build.rs` +/// file: +/// +/// ``` toml +/// [build-dependencies] +/// product_common = { workspace = true, features = ["move-history-manager"] } +/// ``` pub struct MoveHistoryManager { move_lock_path: PathBuf, history_file_path: PathBuf, From d9db8500d5b899fc6bab01430ae61a6bc3732fc5 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 11 Aug 2025 13:41:17 +0200 Subject: [PATCH 17/28] fix fmt issue --- product_common/src/move_history_manager.rs | 30 ++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs index 56db810..5c883da 100644 --- a/product_common/src/move_history_manager.rs +++ b/product_common/src/move_history_manager.rs @@ -99,24 +99,28 @@ impl PackageRegistry { /// /// ```rust /// use std::path::PathBuf; +/// /// use product_common::move_history_manager::MoveHistoryManager; /// /// fn main() { -/// let move_lock_path = "..//Move.lock"; -/// println!("[build.rs] move_lock_path: {move_lock_path}"); -/// let move_history_path = "..//Move.history.json"; -/// println!("[build.rs] move_history_path: {move_history_path}"); +/// let move_lock_path = "..//Move.lock"; +/// println!("[build.rs] move_lock_path: {move_lock_path}"); +/// let move_history_path = "..//Move.history.json"; +/// println!("[build.rs] move_history_path: {move_history_path}"); /// -/// MoveHistoryManager::new( -/// &PathBuf::from(move_lock_path), -/// &PathBuf::from(move_history_path), -/// // Use `Some(vec![])` instead of `None`, if you don't want to ignore `localnet` -/// None, -/// ).manage_history_file(|message| { println!("[build.rs] {}", message); }) -/// .expect("Successfully managed Move history file"); +/// MoveHistoryManager::new( +/// &PathBuf::from(move_lock_path), +/// &PathBuf::from(move_history_path), +/// // Use `Some(vec![])` instead of `None`, if you don't want to ignore `localnet` +/// None, +/// ) +/// .manage_history_file(|message| { +/// println!("[build.rs] {}", message); +/// }) +/// .expect("Successfully managed Move history file"); /// -/// // Tell Cargo to rerun this build script if the Move.lock file changes. -/// println!("cargo::rerun-if-changed={move_lock_path}"); +/// // Tell Cargo to rerun this build script if the Move.lock file changes. +/// println!("cargo::rerun-if-changed={move_lock_path}"); /// } /// ``` /// From dae0cf5f459d6a6605e4b238d8637959a0255af7 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 11 Aug 2025 14:59:33 +0200 Subject: [PATCH 18/28] ignore the build.rs example in rust doctest --- product_common/src/move_history_manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs index 5c883da..0d8fdfa 100644 --- a/product_common/src/move_history_manager.rs +++ b/product_common/src/move_history_manager.rs @@ -97,7 +97,7 @@ impl PackageRegistry { /// * In this example, the Move package is expected be located in a parent directory of the library package. If this is /// not the case, please edit the file paths accordingly /// -/// ```rust +/// ``` ignore /// use std::path::PathBuf; /// /// use product_common::move_history_manager::MoveHistoryManager; From d854daf4ab3dd6d9de96b27c3359a1b3d0fd63af Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Tue, 12 Aug 2025 11:33:46 +0200 Subject: [PATCH 19/28] Further enhance the docs for MoveHistoryManager and PackageRegistry --- product_common/src/move_history_manager.rs | 46 +++++++++++++++++----- product_common/src/package_registry.rs | 32 +++++++++++++++ 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs index 0d8fdfa..28e92d5 100644 --- a/product_common/src/move_history_manager.rs +++ b/product_common/src/move_history_manager.rs @@ -84,18 +84,46 @@ impl PackageRegistry { } /// Manages the content of `Move.history.json` files, including initialization and updates. -/// Provides the main functionality needed to implement a build script in IOTA product repositories. +/// Provides the main functionality needed to implement a `build.rs` script in IOTA product repositories. /// -/// Libraries in IOTA product repositories, depending on Move packages provided in the same repository, +/// Libraries in IOTA product repositories, depending on Move packages **provided in the same repository**, /// should have a `build.rs` script (contained in the libraries root folder), that manages the content of -/// the `Move.history.json` file that is located next to the `Move.lock` file of the Move package. +/// the `Move.history.json` file. /// -/// # Example `build.rs` script -/// This example shows how to use the `MoveHistoryManager` in a build.rs` script. +/// ## `Move.history.json` file +/// The `Move.history.json` file is used to store the history of package versions for Move packages +/// that the library depends on. See the `PackageRegistry` documentation for more details. +/// +/// The `Move.history.json` file: +/// * should be located in the same directory as the `Move.lock` file of the Move package that the library depends on +/// * contains all data that is provided by the `PackageRegistry`, which is used by the library to interact with the +/// Move package +/// * will be integrated into build rust binaries at build time (using include_str!()) when the library is built +/// * should not contain the package versions of the `localnet` environment, as this would probably blow up the size of +/// the file and is not needed for production use cases +/// * should be added to the git repository also containing the library and the Move package +/// * should be updated by a `build.rs` script in the library package, whenever the `Move.lock` file of the Move package +/// changes - see below for a `build.rs` example using the `MoveHistoryManager` +/// +/// ## `build.rs` scripts +/// The `MoveHistoryManager` is designed to be used in a `build.rs` script of a library that depends on a Move package +/// provided in the same repository. The `build.rs` script described in the example below will be build and +/// executed every time when the library, containing the `build.rs` script, is built and the `Move.lock` +/// file of the Move package has changed. +/// +/// When the library is built and the timestamp of the `Move.lock` file has changed, the `MoveHistoryManager` +/// will check if the `Move.lock` file exists and the `Move.history.json` file will be: +/// * created, if a `Move.lock` file exists, but the `Move.history.json` file does not exist yet +/// * updated, if both the `Move.lock` file and the `Move.history.json` file exist +/// +/// If the `Move.lock` file doesn't exist the whole processing is skipped. +/// +/// ## Example `build.rs` script +/// This example shows how to use the `MoveHistoryManager` in a `build.rs` script. /// /// * Please replace `` with the actual name of the Move package -/// * In this example, the Move package is expected be located in a parent directory of the library package. If this is -/// not the case, please edit the file paths accordingly +/// * In this example, the Move package is expected to be located in the parent directory of the library package. If +/// this is not the case, please edit the file paths accordingly /// /// ``` ignore /// use std::path::PathBuf; @@ -124,7 +152,7 @@ impl PackageRegistry { /// } /// ``` /// -/// To use the `MoveHistoryManager` in a `build.rs` script in an IOTA product repository, you need +/// To use the `MoveHistoryManager` in a `build.rs` script in an IOTA product library package, you need /// to add the following build dependency in the `cargo.toml` of the crate containing the `build.rs` /// file: /// @@ -151,7 +179,7 @@ impl MoveHistoryManager { /// A new `MoveHistoryManager` instance. /// /// Doesn't check if any of the provided paths are invalid or if the `Move.lock` file cannot be parsed. - /// Functions `init` and `update` will handle those checks. + /// Functions `manage_history_file`, `init` and `update` will handle those checks. pub fn new(move_lock_path: &Path, history_file_path: &Path, aliases_to_ignore: Option>) -> Self { let aliases_to_ignore = aliases_to_ignore.unwrap_or(vec!["localnet".to_string()]); Self { diff --git a/product_common/src/package_registry.rs b/product_common/src/package_registry.rs index e29e1ee..7b2597c 100644 --- a/product_common/src/package_registry.rs +++ b/product_common/src/package_registry.rs @@ -35,6 +35,38 @@ impl Env { } } +/// A registry that tracks package versions across different blockchain environments. +/// +/// The `PackageRegistry` stores: +/// - Aliases that map human-readable network names (like "mainnet", "testnet") to chain IDs. +/// - Environment mappings that associate chain IDs with the history of package versions. The history of package +/// versions is ordered chronologically, with the latest version at the end of the array. +/// +/// # Initialization using `Move.history.json` files +/// +/// The registry can be initialized from a `Move.history.json` file using the function +/// `from_package_history_json_str()`. A `Move.history.json` file has the following structure: +/// ```json +/// { +/// "aliases": { +/// "networkName": "chainId", +/// // e.g., "mainnet": "6364aad5" +/// }, +/// "envs": { +/// "chainId": ["0xpackageId1", "0xpackageId2"], +/// // Where the last ID is the most recent version +/// } +/// } +/// ``` +/// `Move.history.json` files can automatically be generated and updated using `build.rs` +/// scripts in your Rust projects. The `product_common` crate provides a `MoveHistoryManager` +/// that can be used to manage the `Move.history.json` file. See there for more details. +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +pub struct PackageRegistry { + aliases: HashMap, + envs: HashMap>, +} + #[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct PackageRegistry { aliases: HashMap, From 536382420046d403bf0ed92bcf6f1c455634952c Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Thu, 14 Aug 2025 11:27:42 +0200 Subject: [PATCH 20/28] Remove doubled PackageRegistry struct definition --- product_common/src/package_registry.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/product_common/src/package_registry.rs b/product_common/src/package_registry.rs index 7b2597c..c921e1d 100644 --- a/product_common/src/package_registry.rs +++ b/product_common/src/package_registry.rs @@ -67,12 +67,6 @@ pub struct PackageRegistry { envs: HashMap>, } -#[derive(Debug, Clone, Default, Deserialize, Serialize)] -pub struct PackageRegistry { - aliases: HashMap, - envs: HashMap>, -} - impl PackageRegistry { /// Returns the historical list of this package's versions for a given `chain`. /// `chain` can either be a chain identifier or its alias. From e5e5e4aabe90d05fd3bbd272fab4b0453055a93d Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Thu, 14 Aug 2025 12:36:15 +0200 Subject: [PATCH 21/28] Several enhancements * Narrow down serde_json Value to type String to avoid the need for fn `remove_first_and_last_char_from_string` * Use anyhow `with_context` instead `context` where `format!` is used * Use `serde_json::to_string_pretty` to avoid the need for fn `to_prettified_string` --- product_common/src/move_history_manager.rs | 57 ++++++++++------------ 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs index 28e92d5..eb129f0 100644 --- a/product_common/src/move_history_manager.rs +++ b/product_common/src/move_history_manager.rs @@ -4,21 +4,29 @@ use std::fs; use std::path::{Path, PathBuf}; -use anyhow::Context; +use anyhow::{Context, Result}; use iota_interaction::types::base_types::ObjectID; use super::package_registry::{Env, PackageRegistry}; -fn remove_first_and_last_char_from_string(value: String) -> String { - let mut chars = value.chars(); - chars.next(); - chars.next_back(); - chars.as_str().to_string() -} - -fn to_prettified_string(registry: &PackageRegistry) -> anyhow::Result { - let json_value = serde_json::to_value(registry).context("Failed to serialize PackageRegistry to JSON value")?; - Ok(format!("{json_value:#}")) +/// Helper function to extract an ID field from a TOML table with proper error handling. +/// +/// # Arguments +/// * `table` - The TOML table to extract the value from. +/// * `key` - The key to extract from the table. +/// * `alias` - The environment alias for error context. +/// +/// # Returns +/// The extracted string value or an error with context. +fn get_id_from_table(table: &toml::Table, key: &str, alias: &str) -> Result { + Ok( + table + .get(key) + .with_context(|| format!("invalid Move.lock file: missing `{key}` for env {alias}"))? + .as_str() + .with_context(|| format!("invalid Move.lock file: `{key}` for env {alias} is not a string"))? + .to_string(), + ) } impl PackageRegistry { @@ -49,26 +57,12 @@ impl PackageRegistry { }; let chain_id: String = table .remove("chain-id") - .context(format!("invalid Move.lock file: missing `chain-id` for env {alias}"))? + .with_context(|| format!("invalid Move.lock file: missing `chain-id` for env {alias}"))? .try_into() .context("invalid Move.lock file: invalid `chain-id`")?; - let original_published_id: String = remove_first_and_last_char_from_string( - table - .get("original-published-id") - .context(format!( - "invalid Move.lock file: missing `original-published-id` for env {alias}" - ))? - .to_string(), - ); - let latest_published_id: String = remove_first_and_last_char_from_string( - table - .get("latest-published-id") - .context(format!( - "invalid Move.lock file: missing `latest-published-id` for env {alias}" - ))? - .to_string(), - ); + let original_published_id: String = get_id_from_table(&table, "original-published-id", &alias)?; + let latest_published_id: String = get_id_from_table(&table, "latest-published-id", &alias)?; let mut metadata = vec![ObjectID::from_hex_literal(original_published_id.as_str())?]; if original_published_id != latest_published_id { @@ -158,8 +152,9 @@ impl PackageRegistry { /// /// ``` toml /// [build-dependencies] -/// product_common = { workspace = true, features = ["move-history-manager"] } +/// product_common = { workspace = true, default-features = false, features = ["move-history-manager"] } /// ``` +#[derive(Debug)] pub struct MoveHistoryManager { move_lock_path: PathBuf, history_file_path: PathBuf, @@ -277,7 +272,7 @@ impl MoveHistoryManager { let _ = registry.remove_env_by_alias(alias); } - let json_content = to_prettified_string(®istry)?; + let json_content = serde_json::to_string_pretty(®istry)?; fs::write(&self.history_file_path, json_content) .with_context(|| format!("Failed to write to output file: {}", self.history_file_path.display()))?; @@ -317,7 +312,7 @@ impl MoveHistoryManager { } // Serialize and write updated registry - let updated_json_content = to_prettified_string(®istry)?; + let updated_json_content = serde_json::to_string_pretty(®istry)?; fs::write(&self.history_file_path, updated_json_content).with_context(|| { format!( From 6a0864397286f972734b71a0b7e4f0cdbaed199c Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Thu, 14 Aug 2025 15:18:44 +0200 Subject: [PATCH 22/28] Hand over errors from `update()` or `init()` to users of `manage_history_file()` --- product_common/src/move_history_manager.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs index eb129f0..8909324 100644 --- a/product_common/src/move_history_manager.rs +++ b/product_common/src/move_history_manager.rs @@ -237,18 +237,14 @@ impl MoveHistoryManager { if self.history_file_exists() { // If the output file already exists, update it. console_out(format!("File `{move_history_path}` already exists, updating...")); - self - .update() - .expect("Successfully updating `Move.history.json` file with `Move.lock` content"); + self.update()?; console_out(format!( "Successfully updated`{move_history_path}` with content of `{move_lock_path}`" )); } else { // If the output file does not exist, create it. console_out(format!("File `{move_history_path}` does not exist, creating...")); - self - .init() - .expect("Successfully creating a `Move.history.json` file with `Move.lock` content"); + self.init()?; console_out(format!( "Successfully created file `{move_history_path}` with content of `{move_lock_path}` content" )); From 6c6295b4241033744302da3ae35ab031eb0f6254 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Tue, 23 Sep 2025 12:04:37 +0200 Subject: [PATCH 23/28] Replace `aliases_to_ignore` with `additional_aliases_to_watch` and a default `aliases_to_watch` list --- product_common/src/move_history_manager.rs | 106 +++++++++++++++------ 1 file changed, 78 insertions(+), 28 deletions(-) diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs index 8909324..5f35dee 100644 --- a/product_common/src/move_history_manager.rs +++ b/product_common/src/move_history_manager.rs @@ -34,13 +34,16 @@ impl PackageRegistry { /// /// # Arguments /// * `move_lock` - A string containing the content of the `Move.lock` file. + /// * `aliases_to_watch` - A vector of environment aliases to include in the registry. + /// Only environments with aliases in this list will be processed and added to the registry. + /// Other environments in the `Move.lock` file will be ignored. /// /// # Returns /// A `PackageRegistry` instance populated with data from the `Move.lock` file. /// /// # Errors /// Returns an error if the `Move.lock` file content is invalid or cannot be parsed. - pub fn from_move_lock_content(move_lock: &str) -> anyhow::Result { + pub fn from_move_lock_content(move_lock: &str, aliases_to_watch: &Vec) -> anyhow::Result { let mut move_lock: toml::Table = move_lock.parse()?; let mut move_lock_iter = move_lock @@ -52,6 +55,9 @@ impl PackageRegistry { .into_iter(); move_lock_iter.try_fold(Self::default(), |mut registry, (alias, table)| { + if !aliases_to_watch.contains(&alias) { + return Ok(registry); + } let toml::Value::Table(mut table) = table else { anyhow::bail!("invalid Move.lock file: invalid `env` table"); }; @@ -133,8 +139,10 @@ impl PackageRegistry { /// MoveHistoryManager::new( /// &PathBuf::from(move_lock_path), /// &PathBuf::from(move_history_path), -/// // Use `Some(vec![])` instead of `None`, if you don't want to ignore `localnet` -/// None, +/// // We will watch the default watch list (`get_default_aliases_to_watch()`) in this build script +/// // so we leave the `additional_aliases_to_watch` argument vec empty. +/// // Use for example `vec!["localnet".to_string()]` instead, if you don't want to ignore `localnet`. +/// vec![], /// ) /// .manage_history_file(|message| { /// println!("[build.rs] {}", message); @@ -158,7 +166,7 @@ impl PackageRegistry { pub struct MoveHistoryManager { move_lock_path: PathBuf, history_file_path: PathBuf, - aliases_to_ignore: Vec, + aliases_to_watch: Vec, } impl MoveHistoryManager { @@ -167,23 +175,46 @@ impl MoveHistoryManager { /// # Arguments /// * `move_lock_path` - Path to the `Move.lock` file. /// * `history_file_path` - Path to the `M̀ove.history.toml` file. - /// * `aliases_to_ignore` - Optional list of environment aliases to ignore. If `aliases_to_ignore` is not provided, it - /// defaults to `["localnet"]`. + /// * `additional_aliases_to_watch` - List of environment aliases to be watched additionally to those + /// environments, being watched per default (see function `get_default_aliases_to_watch()` for more details). + /// Examples: + /// * Watch only defaults environments: `new(move_lock_path, history_file_path, vec![])` + /// * Additionally watch the `localnet` environment: `new(move_lock_path, history_file_path, vec!["localnet"])` /// /// # Returns /// A new `MoveHistoryManager` instance. /// /// Doesn't check if any of the provided paths are invalid or if the `Move.lock` file cannot be parsed. /// Functions `manage_history_file`, `init` and `update` will handle those checks. - pub fn new(move_lock_path: &Path, history_file_path: &Path, aliases_to_ignore: Option>) -> Self { - let aliases_to_ignore = aliases_to_ignore.unwrap_or(vec!["localnet".to_string()]); + pub fn new(move_lock_path: &Path, history_file_path: &Path, mut additional_aliases_to_watch: Vec) -> Self { + let mut aliases_to_watch = Self::get_default_aliases_to_watch(); + aliases_to_watch.append(&mut additional_aliases_to_watch); + Self { move_lock_path: move_lock_path.to_owned(), history_file_path: history_file_path.to_owned(), - aliases_to_ignore, + aliases_to_watch, } } + /// Returns the default list of environment aliases being watched if no additional + /// `additional_aliases_to_watch` is provided in the `new()` function. + /// Returns a vector containing `mainnet`, `testnet` and `devnet`. + /// + /// Use the `additional_aliases_to_watch` argument of the `new()` function to specify aliases + /// of additional environments to be watched, e.g. `localnet`. + /// + /// Use method `aliases_to_watch()` to evaluate the complete list of environment aliases being watched + /// by a `MoveHistoryManager` instance. + pub fn get_default_aliases_to_watch() -> Vec { + vec!["mainnet".to_string(), "testnet".to_string(), "devnet".to_string()] + } + + /// Returns the list of environment aliases being watched by this `MoveHistoryManager` instance. + pub fn aliases_to_watch(&self) -> &Vec { + &self.aliases_to_watch + } + /// Checks if the Move.history.json file exists. pub fn history_file_exists(&self) -> bool { self.history_file_path.exists() && self.history_file_path.is_file() @@ -194,11 +225,6 @@ impl MoveHistoryManager { self.move_lock_path.exists() && self.move_lock_path.is_file() } - /// Returns the list of environment aliases to ignore. - pub fn aliases_to_ignore(&self) -> &[String] { - &self.aliases_to_ignore - } - /// Returns the path to the Move.lock file. pub fn move_lock_path(&self) -> &Path { &self.move_lock_path @@ -256,17 +282,16 @@ impl MoveHistoryManager { } /// Creates an initial Move.history.json file from a Move.lock file - /// Will ignore any environment aliases specified in `aliases_to_ignore`. + /// Will only take those environment aliases into account, listed in `aliases_to_watch()`. pub fn init(&self) -> anyhow::Result<()> { let move_lock_content = fs::read_to_string(&self.move_lock_path) .with_context(|| format!("Failed to read Move.lock file: {}", &self.move_lock_path.display()))?; - let mut registry = - PackageRegistry::from_move_lock_content(&move_lock_content).context("Failed to parse Move.lock file")?; - - for alias in self.aliases_to_ignore.iter() { - let _ = registry.remove_env_by_alias(alias); - } + let registry = + PackageRegistry::from_move_lock_content( + &move_lock_content, + &self.aliases_to_watch, + ).context("Failed to parse Move.lock file")?; let json_content = serde_json::to_string_pretty(®istry)?; @@ -293,12 +318,11 @@ impl MoveHistoryManager { let move_lock_content = fs::read_to_string(&self.move_lock_path) .with_context(|| format!("Failed to read Move.lock file: {}", self.move_lock_path.display()))?; - let mut new_registry = - PackageRegistry::from_move_lock_content(&move_lock_content).context("Failed to parse Move.lock file")?; - - for alias in self.aliases_to_ignore.iter() { - let _ = new_registry.remove_env_by_alias(alias); - } + let new_registry = + PackageRegistry::from_move_lock_content( + &move_lock_content, + &self.aliases_to_watch, + ).context("Failed to parse Move.lock file")?; // Add new package versions from Move.lock to existing registry for (chain_id, versions) in new_registry.envs().iter() { @@ -392,7 +416,7 @@ latest-published-id = "0xfbddb4631d027b2c4f0b4b90c020713d258ed32bdb342b5397f4da7 } } - let history_manager = MoveHistoryManager::new(&move_lock_path, &history_path, None); + let history_manager = MoveHistoryManager::new(&move_lock_path, &history_path, vec![]); (temp_dir, history_path, move_lock_path, history_manager) } @@ -412,6 +436,8 @@ latest-published-id = "0xfbddb4631d027b2c4f0b4b90c020713d258ed32bdb342b5397f4da7 assert!(content.contains("\"aliases\": {")); assert!(content.contains("\"mainnet\": \"6364aad5\"")); assert!(content.contains("\"testnet\": \"2304aa97\"")); + // localnet should not be included per default + assert!(!content.contains("\"localnet\": \"ecc0606a\"")); } #[test] @@ -569,4 +595,28 @@ latest-published-id = "0x0d88bcecde97585d50207a029a85d7ea0bacf73ab741cbaa975a6e2 fs::create_dir(&history_path).unwrap(); assert!(!history_manager.history_file_exists()); } + + #[test] + fn new_includes_additional_aliases_to_watch() { + let move_lock_path = PathBuf::from("Move.lock"); + let history_file_path = PathBuf::from("Move.history.json"); + let additional = vec!["localnet".to_string(), "customnet".to_string()]; + let manager = MoveHistoryManager::new(&move_lock_path, &history_file_path, additional.clone()); + + let mut expected = MoveHistoryManager::get_default_aliases_to_watch(); + expected.extend(additional); + + assert_eq!(manager.aliases_to_watch(), &expected); + } + + #[test] + fn aliases_to_watch_returns_only_defaults_when_no_additional_provided() { + let move_lock_path = PathBuf::from("Move.lock"); + let history_file_path = PathBuf::from("Move.history.json"); + let manager = MoveHistoryManager::new(&move_lock_path, &history_file_path, vec![]); + + let expected = MoveHistoryManager::get_default_aliases_to_watch(); + assert_eq!(manager.aliases_to_watch(), &expected); + } } + From 23acc3a6b90788c53c8174604cdc292b955b8dcf Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 29 Sep 2025 09:49:29 +0200 Subject: [PATCH 24/28] Fix clippy anf fmt issues --- product_common/src/move_history_manager.rs | 38 +++++++++------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs index 5f35dee..97e59cd 100644 --- a/product_common/src/move_history_manager.rs +++ b/product_common/src/move_history_manager.rs @@ -34,16 +34,16 @@ impl PackageRegistry { /// /// # Arguments /// * `move_lock` - A string containing the content of the `Move.lock` file. - /// * `aliases_to_watch` - A vector of environment aliases to include in the registry. - /// Only environments with aliases in this list will be processed and added to the registry. - /// Other environments in the `Move.lock` file will be ignored. + /// * `aliases_to_watch` - A vector of environment aliases to include in the registry. Only environments with aliases + /// in this list will be processed and added to the registry. Other environments in the `Move.lock` file will be + /// ignored. /// /// # Returns /// A `PackageRegistry` instance populated with data from the `Move.lock` file. /// /// # Errors /// Returns an error if the `Move.lock` file content is invalid or cannot be parsed. - pub fn from_move_lock_content(move_lock: &str, aliases_to_watch: &Vec) -> anyhow::Result { + pub fn from_move_lock_content(move_lock: &str, aliases_to_watch: &[String]) -> anyhow::Result { let mut move_lock: toml::Table = move_lock.parse()?; let mut move_lock_iter = move_lock @@ -55,9 +55,9 @@ impl PackageRegistry { .into_iter(); move_lock_iter.try_fold(Self::default(), |mut registry, (alias, table)| { - if !aliases_to_watch.contains(&alias) { - return Ok(registry); - } + if !aliases_to_watch.contains(&alias) { + return Ok(registry); + } let toml::Value::Table(mut table) = table else { anyhow::bail!("invalid Move.lock file: invalid `env` table"); }; @@ -175,11 +175,10 @@ impl MoveHistoryManager { /// # Arguments /// * `move_lock_path` - Path to the `Move.lock` file. /// * `history_file_path` - Path to the `M̀ove.history.toml` file. - /// * `additional_aliases_to_watch` - List of environment aliases to be watched additionally to those - /// environments, being watched per default (see function `get_default_aliases_to_watch()` for more details). - /// Examples: - /// * Watch only defaults environments: `new(move_lock_path, history_file_path, vec![])` - /// * Additionally watch the `localnet` environment: `new(move_lock_path, history_file_path, vec!["localnet"])` + /// * `additional_aliases_to_watch` - List of environment aliases to be watched additionally to those environments, + /// being watched per default (see function `get_default_aliases_to_watch()` for more details). Examples: + /// * Watch only defaults environments: `new(move_lock_path, history_file_path, vec![])` + /// * Additionally watch the `localnet` environment: `new(move_lock_path, history_file_path, vec!["localnet"])` /// /// # Returns /// A new `MoveHistoryManager` instance. @@ -287,11 +286,8 @@ impl MoveHistoryManager { let move_lock_content = fs::read_to_string(&self.move_lock_path) .with_context(|| format!("Failed to read Move.lock file: {}", &self.move_lock_path.display()))?; - let registry = - PackageRegistry::from_move_lock_content( - &move_lock_content, - &self.aliases_to_watch, - ).context("Failed to parse Move.lock file")?; + let registry = PackageRegistry::from_move_lock_content(&move_lock_content, &self.aliases_to_watch) + .context("Failed to parse Move.lock file")?; let json_content = serde_json::to_string_pretty(®istry)?; @@ -318,11 +314,8 @@ impl MoveHistoryManager { let move_lock_content = fs::read_to_string(&self.move_lock_path) .with_context(|| format!("Failed to read Move.lock file: {}", self.move_lock_path.display()))?; - let new_registry = - PackageRegistry::from_move_lock_content( - &move_lock_content, - &self.aliases_to_watch, - ).context("Failed to parse Move.lock file")?; + let new_registry = PackageRegistry::from_move_lock_content(&move_lock_content, &self.aliases_to_watch) + .context("Failed to parse Move.lock file")?; // Add new package versions from Move.lock to existing registry for (chain_id, versions) in new_registry.envs().iter() { @@ -619,4 +612,3 @@ latest-published-id = "0x0d88bcecde97585d50207a029a85d7ea0bacf73ab741cbaa975a6e2 assert_eq!(manager.aliases_to_watch(), &expected); } } - From 7dccb6f76af1f7ac18f99ab28fd4355274eb9cff Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 29 Sep 2025 10:28:54 +0200 Subject: [PATCH 25/28] Aline indexmap dependency with iota repo requirements --- iota_interaction/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iota_interaction/Cargo.toml b/iota_interaction/Cargo.toml index 76d7560..b626cb5 100644 --- a/iota_interaction/Cargo.toml +++ b/iota_interaction/Cargo.toml @@ -17,7 +17,7 @@ async-trait = { version = "0.1.81", default-features = false } bcs.workspace = true cfg-if.workspace = true fastcrypto = { git = "https://github.com/MystenLabs/fastcrypto", rev = "69d496c71fb37e3d22fe85e5bbfd4256d61422b9", package = "fastcrypto", features = ["copy_key"] } -indexmap = "2.9" +indexmap = "2.1.0" jsonpath-rust = { version = "0.5.1", optional = true } secret-storage.workspace = true serde.workspace = true From c4d304e914870ffa87b68568378595a11db921e9 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 29 Sep 2025 10:28:54 +0200 Subject: [PATCH 26/28] Aligne indexmap dependency with iota repo requirements --- iota_interaction/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iota_interaction/Cargo.toml b/iota_interaction/Cargo.toml index 76d7560..b626cb5 100644 --- a/iota_interaction/Cargo.toml +++ b/iota_interaction/Cargo.toml @@ -17,7 +17,7 @@ async-trait = { version = "0.1.81", default-features = false } bcs.workspace = true cfg-if.workspace = true fastcrypto = { git = "https://github.com/MystenLabs/fastcrypto", rev = "69d496c71fb37e3d22fe85e5bbfd4256d61422b9", package = "fastcrypto", features = ["copy_key"] } -indexmap = "2.9" +indexmap = "2.1.0" jsonpath-rust = { version = "0.5.1", optional = true } secret-storage.workspace = true serde.workspace = true From e5b8861d522a3c4fc86b5fd94ca4bc5e65c207ab Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 29 Sep 2025 10:45:30 +0200 Subject: [PATCH 27/28] Reduce nesting with early returns --- product_common/src/move_history_manager.rs | 37 ++++++++++++---------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs index 97e59cd..6b63b23 100644 --- a/product_common/src/move_history_manager.rs +++ b/product_common/src/move_history_manager.rs @@ -258,24 +258,27 @@ impl MoveHistoryManager { pub fn manage_history_file(&self, console_out: impl Fn(String)) -> anyhow::Result<()> { let move_history_path = self.history_file_path.to_string_lossy(); let move_lock_path = self.move_lock_path.to_string_lossy(); - if self.move_lock_file_exists() { - if self.history_file_exists() { - // If the output file already exists, update it. - console_out(format!("File `{move_history_path}` already exists, updating...")); - self.update()?; - console_out(format!( - "Successfully updated`{move_history_path}` with content of `{move_lock_path}`" - )); - } else { - // If the output file does not exist, create it. - console_out(format!("File `{move_history_path}` does not exist, creating...")); - self.init()?; - console_out(format!( - "Successfully created file `{move_history_path}` with content of `{move_lock_path}` content" - )); - } + + if !self.move_lock_file_exists() { + console_out(format!("File `{move_lock_path}` does not exist, skipping...")); + return Ok(()) + } + + // The move_lock_file exists + if self.history_file_exists() { + // If the output file already exists, update it. + console_out(format!("File `{move_history_path}` already exists, updating...")); + self.update()?; + console_out(format!( + "Successfully updated`{move_history_path}` with content of `{move_lock_path}`" + )); } else { - console_out(format!("File `{move_history_path}` does not exist, skipping...")); + // If the output file does not exist, create it. + console_out(format!("File `{move_history_path}` does not exist, creating...")); + self.init()?; + console_out(format!( + "Successfully created file `{move_history_path}` with content of `{move_lock_path}` content" + )); } Ok(()) } From 86c15a02e3c753787a3eba9fbad5a62460c628fe Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 29 Sep 2025 13:59:14 +0200 Subject: [PATCH 28/28] Fix fmt issue --- product_common/src/move_history_manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/product_common/src/move_history_manager.rs b/product_common/src/move_history_manager.rs index 6b63b23..2117cfa 100644 --- a/product_common/src/move_history_manager.rs +++ b/product_common/src/move_history_manager.rs @@ -261,7 +261,7 @@ impl MoveHistoryManager { if !self.move_lock_file_exists() { console_out(format!("File `{move_lock_path}` does not exist, skipping...")); - return Ok(()) + return Ok(()); } // The move_lock_file exists