From 0b7ebb6003a5f2bf32459a4c66e04f139c66b77b Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Wed, 6 May 2026 20:25:01 -0700 Subject: [PATCH 1/6] Fix YAML sequence indentation in manifest and mock data files --- src/commands/edge_app/instance_manifest.rs | 4 +- src/commands/edge_app/manifest.rs | 20 ++++----- src/commands/edge_app/server.rs | 3 +- src/commands/serde_utils.rs | 47 ++++++++++++++++++++++ 4 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/commands/edge_app/instance_manifest.rs b/src/commands/edge_app/instance_manifest.rs index 8c138f9..dc1f385 100644 --- a/src/commands/edge_app/instance_manifest.rs +++ b/src/commands/edge_app/instance_manifest.rs @@ -8,7 +8,7 @@ use serde_with::serde_as; use crate::commands::edge_app::manifest::beautify_error_message; use crate::commands::serde_utils::{ - deserialize_option_string_field, string_field_is_none_or_empty, + deserialize_option_string_field, indent_yaml_sequences, string_field_is_none_or_empty, }; use crate::commands::CommandError; @@ -123,7 +123,7 @@ impl InstanceManifest { } pub fn save_to_file(manifest: &InstanceManifest, path: &Path) -> Result<(), CommandError> { - let yaml = serde_yaml::to_string(&manifest)?; + let yaml = indent_yaml_sequences(&serde_yaml::to_string(&manifest)?); let manifest_file = File::create(path)?; write!(&manifest_file, "---\n{yaml}")?; Ok(()) diff --git a/src/commands/edge_app/manifest.rs b/src/commands/edge_app/manifest.rs index 162f408..48c94ac 100644 --- a/src/commands/edge_app/manifest.rs +++ b/src/commands/edge_app/manifest.rs @@ -10,7 +10,7 @@ use serde_json::json; use super::manifest_auth::AuthType; use crate::api::edge_app::setting::{deserialize_settings, serialize_settings, Setting}; use crate::commands::serde_utils::{ - deserialize_option_string_field, string_field_is_none_or_empty, + deserialize_option_string_field, indent_yaml_sequences, string_field_is_none_or_empty, }; use crate::commands::CommandError; @@ -270,7 +270,7 @@ impl EdgeAppManifest { } pub fn save_to_file(manifest: &EdgeAppManifest, path: &Path) -> Result<(), CommandError> { - let yaml = serde_yaml::to_string(&manifest)?; + let yaml = indent_yaml_sequences(&serde_yaml::to_string(&manifest)?); let manifest_file = File::create(path)?; write!(&manifest_file, "---\n{yaml}")?; Ok(()) @@ -405,8 +405,8 @@ icon: test_icon author: test_author homepage_url: test_url categories: -- Utilities -- Dashboards + - Utilities + - Dashboards entrypoint: type: file ready_signal: true @@ -442,8 +442,8 @@ user_version: test_version icon: test_icon homepage_url: test_url categories: -- Utilities -- Dashboards + - Utilities + - Dashboards entrypoint: type: file settings: @@ -477,8 +477,8 @@ user_version: test_version icon: test_icon homepage_url: test_url categories: -- Utilities -- Dashboards + - Utilities + - Dashboards entrypoint: type: file ready_signal: true @@ -939,8 +939,8 @@ icon: test_icon author: test_author homepage_url: test_url categories: -- Utilities -- Dashboards + - Utilities + - Dashboards entrypoint: type: file settings: diff --git a/src/commands/edge_app/server.rs b/src/commands/edge_app/server.rs index ccff49d..d62550c 100644 --- a/src/commands/edge_app/server.rs +++ b/src/commands/edge_app/server.rs @@ -14,6 +14,7 @@ use crate::api::edge_app::setting::SettingType; use crate::commands::edge_app::manifest::EdgeAppManifest; use crate::commands::edge_app::EdgeAppCommand; use crate::commands::ignorer::Ignorer; +use crate::commands::serde_utils::indent_yaml_sequences; use crate::commands::CommandError; pub const MOCK_DATA_FILENAME: &str = "mock-data.yml"; @@ -331,7 +332,7 @@ impl EdgeAppCommand { ); mock_data.insert("settings".to_string(), serde_yaml::to_value(settings)?); - let mock_data_yaml = serde_yaml::to_string(&mock_data)?; + let mock_data_yaml = indent_yaml_sequences(&serde_yaml::to_string(&mock_data)?); fs::write(edge_app_dir.join(MOCK_DATA_FILENAME), mock_data_yaml)?; diff --git a/src/commands/serde_utils.rs b/src/commands/serde_utils.rs index 4bb6209..92fd6fe 100644 --- a/src/commands/serde_utils.rs +++ b/src/commands/serde_utils.rs @@ -1,5 +1,52 @@ use serde::{Deserialize, Deserializer}; +pub fn indent_yaml_sequences(yaml: &str) -> String { + let mut result = String::with_capacity(yaml.len() + 32); + let mut lines = yaml.lines().peekable(); + + while let Some(line) = lines.next() { + result.push_str(line); + result.push('\n'); + + let trimmed = line.trim_end(); + if !trimmed.ends_with(':') { + continue; + } + + let key_indent = line.len() - line.trim_start().len(); + let extra = " "; + + while let Some(&next) = lines.peek() { + let next_trimmed = next.trim_start(); + let next_indent = next.len() - next_trimmed.len(); + if (next_trimmed.starts_with("- ") || next_trimmed == "-") && next_indent == key_indent + { + lines.next(); + result.push_str(extra); + result.push_str(next); + result.push('\n'); + + while let Some(&cont) = lines.peek() { + let cont_trimmed = cont.trim_start(); + let cont_indent = cont.len() - cont_trimmed.len(); + if cont_indent > key_indent { + lines.next(); + result.push_str(extra); + result.push_str(cont); + result.push('\n'); + } else { + break; + } + } + } else { + break; + } + } + } + + result +} + pub fn deserialize_option_string_field<'de, D>( field_name: &'static str, error_on_empty: bool, From fd316845cc4abd78e6c629f2bdb349bdff84a1d9 Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Wed, 6 May 2026 21:09:54 -0700 Subject: [PATCH 2/6] Replace hand-rolled YAML formatter with pretty_yaml --- Cargo.lock | 75 ++++++++++++++++++++-- Cargo.toml | 1 + src/commands/edge_app/instance_manifest.rs | 4 +- src/commands/edge_app/manifest.rs | 4 +- src/commands/edge_app/server.rs | 4 +- src/commands/serde_utils.rs | 55 +++------------- 6 files changed, 88 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc4d64c..9a6c53d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -581,6 +581,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "countme" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1277,7 +1283,7 @@ dependencies = [ "hashbrown 0.14.5", "new_debug_unreachable", "once_cell", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "triomphe", ] @@ -2392,6 +2398,17 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "pretty_yaml" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5db2b0044c0dae209c173c6049f210c9449092b07304c9c8b8d555c606e93e" +dependencies = [ + "rowan", + "tiny_pretty", + "yaml_parser", +] + [[package]] name = "prettytable-rs" version = "0.10.0" @@ -2716,6 +2733,18 @@ dependencies = [ "syn", ] +[[package]] +name = "rowan" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21" +dependencies = [ + "countme", + "hashbrown 0.14.5", + "rustc-hash 1.1.0", + "text-size", +] + [[package]] name = "rpassword" version = "7.4.0" @@ -2743,6 +2772,12 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -2925,6 +2960,7 @@ dependencies = [ "indicatif", "log", "openssl", + "pretty_yaml", "prettytable-rs", "protobuf", "rayon", @@ -3444,7 +3480,7 @@ dependencies = [ "from_variant", "num-bigint", "once_cell", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "siphasher 0.3.11", "swc_atoms", @@ -3466,7 +3502,7 @@ dependencies = [ "num-bigint", "once_cell", "phf", - "rustc-hash", + "rustc-hash 2.1.1", "string_enum", "swc_atoms", "swc_common", @@ -3484,7 +3520,7 @@ dependencies = [ "either", "num-bigint", "phf", - "rustc-hash", + "rustc-hash 2.1.1", "seq-macro", "serde", "smartstring", @@ -3629,6 +3665,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "text-size" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" + [[package]] name = "thiserror" version = "1.0.69" @@ -3702,6 +3744,12 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny_pretty" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650d82e943da333637be9f1567d33d605e76810a26464edfd7ae74f7ef181e95" + [[package]] name = "tinystr" version = "0.8.2" @@ -4498,6 +4546,15 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.46.0" @@ -4510,6 +4567,16 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "yaml_parser" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71ebb04c72edf699612d543f6c421142527b85ac180156017fa26be49dc0762f" +dependencies = [ + "rowan", + "winnow", +] + [[package]] name = "yoke" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 24b13e1..fec029a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ warp = "0.3" # MCP server dependencies rmcp = { version = "0.12", features = ["schemars", "server", "transport-io"] } schemars = "0.8" +pretty_yaml = "0.6.0" [dev-dependencies] envtestkit = "1.1.2" diff --git a/src/commands/edge_app/instance_manifest.rs b/src/commands/edge_app/instance_manifest.rs index dc1f385..6e0e0f5 100644 --- a/src/commands/edge_app/instance_manifest.rs +++ b/src/commands/edge_app/instance_manifest.rs @@ -8,7 +8,7 @@ use serde_with::serde_as; use crate::commands::edge_app::manifest::beautify_error_message; use crate::commands::serde_utils::{ - deserialize_option_string_field, indent_yaml_sequences, string_field_is_none_or_empty, + deserialize_option_string_field, format_yaml, string_field_is_none_or_empty, }; use crate::commands::CommandError; @@ -123,7 +123,7 @@ impl InstanceManifest { } pub fn save_to_file(manifest: &InstanceManifest, path: &Path) -> Result<(), CommandError> { - let yaml = indent_yaml_sequences(&serde_yaml::to_string(&manifest)?); + let yaml = format_yaml(&serde_yaml::to_string(&manifest)?); let manifest_file = File::create(path)?; write!(&manifest_file, "---\n{yaml}")?; Ok(()) diff --git a/src/commands/edge_app/manifest.rs b/src/commands/edge_app/manifest.rs index 48c94ac..3fb6146 100644 --- a/src/commands/edge_app/manifest.rs +++ b/src/commands/edge_app/manifest.rs @@ -10,7 +10,7 @@ use serde_json::json; use super::manifest_auth::AuthType; use crate::api::edge_app::setting::{deserialize_settings, serialize_settings, Setting}; use crate::commands::serde_utils::{ - deserialize_option_string_field, indent_yaml_sequences, string_field_is_none_or_empty, + deserialize_option_string_field, format_yaml, string_field_is_none_or_empty, }; use crate::commands::CommandError; @@ -270,7 +270,7 @@ impl EdgeAppManifest { } pub fn save_to_file(manifest: &EdgeAppManifest, path: &Path) -> Result<(), CommandError> { - let yaml = indent_yaml_sequences(&serde_yaml::to_string(&manifest)?); + let yaml = format_yaml(&serde_yaml::to_string(&manifest)?); let manifest_file = File::create(path)?; write!(&manifest_file, "---\n{yaml}")?; Ok(()) diff --git a/src/commands/edge_app/server.rs b/src/commands/edge_app/server.rs index d62550c..b9ce869 100644 --- a/src/commands/edge_app/server.rs +++ b/src/commands/edge_app/server.rs @@ -14,7 +14,7 @@ use crate::api::edge_app::setting::SettingType; use crate::commands::edge_app::manifest::EdgeAppManifest; use crate::commands::edge_app::EdgeAppCommand; use crate::commands::ignorer::Ignorer; -use crate::commands::serde_utils::indent_yaml_sequences; +use crate::commands::serde_utils::format_yaml; use crate::commands::CommandError; pub const MOCK_DATA_FILENAME: &str = "mock-data.yml"; @@ -332,7 +332,7 @@ impl EdgeAppCommand { ); mock_data.insert("settings".to_string(), serde_yaml::to_value(settings)?); - let mock_data_yaml = indent_yaml_sequences(&serde_yaml::to_string(&mock_data)?); + let mock_data_yaml = format_yaml(&serde_yaml::to_string(&mock_data)?); fs::write(edge_app_dir.join(MOCK_DATA_FILENAME), mock_data_yaml)?; diff --git a/src/commands/serde_utils.rs b/src/commands/serde_utils.rs index 92fd6fe..db93222 100644 --- a/src/commands/serde_utils.rs +++ b/src/commands/serde_utils.rs @@ -1,50 +1,15 @@ +use pretty_yaml::config::{FormatOptions, LanguageOptions, Quotes}; use serde::{Deserialize, Deserializer}; -pub fn indent_yaml_sequences(yaml: &str) -> String { - let mut result = String::with_capacity(yaml.len() + 32); - let mut lines = yaml.lines().peekable(); - - while let Some(line) = lines.next() { - result.push_str(line); - result.push('\n'); - - let trimmed = line.trim_end(); - if !trimmed.ends_with(':') { - continue; - } - - let key_indent = line.len() - line.trim_start().len(); - let extra = " "; - - while let Some(&next) = lines.peek() { - let next_trimmed = next.trim_start(); - let next_indent = next.len() - next_trimmed.len(); - if (next_trimmed.starts_with("- ") || next_trimmed == "-") && next_indent == key_indent - { - lines.next(); - result.push_str(extra); - result.push_str(next); - result.push('\n'); - - while let Some(&cont) = lines.peek() { - let cont_trimmed = cont.trim_start(); - let cont_indent = cont.len() - cont_trimmed.len(); - if cont_indent > key_indent { - lines.next(); - result.push_str(extra); - result.push_str(cont); - result.push('\n'); - } else { - break; - } - } - } else { - break; - } - } - } - - result +pub fn format_yaml(raw: &str) -> String { + let options = FormatOptions { + language: LanguageOptions { + quotes: Quotes::PreferSingle, + ..Default::default() + }, + ..Default::default() + }; + pretty_yaml::format_text(raw, &options).unwrap_or_else(|_| raw.to_owned()) } pub fn deserialize_option_string_field<'de, D>( From 751b28dbff41b8e749f1177f7b50a52369648b7b Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Wed, 6 May 2026 21:35:45 -0700 Subject: [PATCH 3/6] Add assertions to test_generate_mock_data_creates_file_with_expected_content --- src/commands/edge_app/server.rs | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/commands/edge_app/server.rs b/src/commands/edge_app/server.rs index b9ce869..b19be4b 100644 --- a/src/commands/edge_app/server.rs +++ b/src/commands/edge_app/server.rs @@ -485,7 +485,6 @@ settings: { let dir = tempdir().unwrap(); let file_path = dir.path().join("test_manifest.yml"); - // The EdgeAppManifest structure from your example let manifest = create_edge_app_manifest_for_test(vec![ Setting { name: "asetting".to_string(), @@ -513,20 +512,15 @@ settings: { let mock_data_path = dir.path().join(MOCK_DATA_FILENAME); assert!(mock_data_path.exists()); - let _generated_content = fs::read_to_string(&mock_data_path).unwrap(); - let _expected_content = r#"metadata: - coordinates: - - "37.3861" - - "-122.0839" - hostname: "srly-t6kb0ta1jrd9o0w" - location: "Code Cafe, Mountain View, California" - screen_name: "Code Cafe Display" - tags: - - "All Screens" - settings: - asetting: "yes" - nsetting: "" - "#; + let generated_content = fs::read_to_string(&mock_data_path).unwrap(); + + // Sequences must be indented under their parent key. + assert!(generated_content.contains("coordinates:\n - ")); + assert!(generated_content.contains("tags:\n - ")); + + // Settings values must be present. + assert!(generated_content.contains("asetting: yes")); + assert!(generated_content.contains("nsetting: ''")); } #[test] From 6b6167219f5daac4b62088dc3c5746ab78cf4ffd Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Thu, 7 May 2026 11:54:30 -0700 Subject: [PATCH 4/6] Move pretty_yaml out of MCP server dependencies section --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fec029a..af3e0f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ log = { version = "0.4.17", features = [ ] } openssl = { version = '0.10', features = ["vendored"] } prettytable-rs = "0.10.0" +pretty_yaml = "0.6.0" protobuf = "3.3.0" rayon = "1.7.0" regex = "1.9.3" @@ -52,7 +53,6 @@ warp = "0.3" # MCP server dependencies rmcp = { version = "0.12", features = ["schemars", "server", "transport-io"] } schemars = "0.8" -pretty_yaml = "0.6.0" [dev-dependencies] envtestkit = "1.1.2" From 1fe85c44d16282480b11b04b36f3e1615132c89f Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Thu, 7 May 2026 12:47:03 -0700 Subject: [PATCH 5/6] Bump nix-installer-action from v8 to v22 --- .github/workflows/nix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 751a689..fa4f7ac 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -36,7 +36,7 @@ jobs: uses: actions/checkout@v4 - name: Install nix - uses: DeterminateSystems/nix-installer-action@v8 + uses: DeterminateSystems/nix-installer-action@v22 - name: Setup FlakeHub Cache uses: DeterminateSystems/flakehub-cache-action@main From a727e80b7831009aaaf1cc6185c0aaa46459269b Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Fri, 8 May 2026 11:19:04 -0700 Subject: [PATCH 6/6] Restore original test structure for generate_mock_data --- src/commands/edge_app/server.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/commands/edge_app/server.rs b/src/commands/edge_app/server.rs index b19be4b..bd63f1e 100644 --- a/src/commands/edge_app/server.rs +++ b/src/commands/edge_app/server.rs @@ -512,15 +512,20 @@ settings: { let mock_data_path = dir.path().join(MOCK_DATA_FILENAME); assert!(mock_data_path.exists()); - let generated_content = fs::read_to_string(&mock_data_path).unwrap(); - - // Sequences must be indented under their parent key. - assert!(generated_content.contains("coordinates:\n - ")); - assert!(generated_content.contains("tags:\n - ")); - - // Settings values must be present. - assert!(generated_content.contains("asetting: yes")); - assert!(generated_content.contains("nsetting: ''")); + let _generated_content = fs::read_to_string(&mock_data_path).unwrap(); + let _expected_content = r#"metadata: + coordinates: + - "37.3861" + - "-122.0839" + hostname: "srly-t6kb0ta1jrd9o0w" + location: "Code Cafe, Mountain View, California" + screen_name: "Code Cafe Display" + tags: + - "All Screens" + settings: + asetting: "yes" + nsetting: "" + "#; } #[test]