diff --git a/crates/turborepo-lib/src/boundaries/config.rs b/crates/turborepo-lib/src/boundaries/config.rs index 1f0e511d3aa86..a2a657c890ccb 100644 --- a/crates/turborepo-lib/src/boundaries/config.rs +++ b/crates/turborepo-lib/src/boundaries/config.rs @@ -31,6 +31,8 @@ pub struct Rule { #[derive(Serialize, Default, Debug, Clone, Iterable, Deserializable, PartialEq)] pub struct Permissions { + #[serde(skip_serializing_if = "Option::is_none")] pub allow: Option>>>, + #[serde(skip_serializing_if = "Option::is_none")] pub deny: Option>>>, } diff --git a/crates/turborepo-lib/src/turbo_json/mod.rs b/crates/turborepo-lib/src/turbo_json/mod.rs index f72f19d71587b..5315d2a462643 100644 --- a/crates/turborepo-lib/src/turbo_json/mod.rs +++ b/crates/turborepo-lib/src/turbo_json/mod.rs @@ -1102,4 +1102,79 @@ mod tests { assert!(raw_config.future_flags.is_none()); } } + + #[test] + fn test_boundaries_permissions_serialization_skip_none() { + let json_with_partial_permissions = r#"{ + "boundaries": { + "dependencies": { + "allow": ["package-a"] + } + } + }"#; + + let parsed: RawTurboJson = + RawRootTurboJson::parse(json_with_partial_permissions, "turbo.json") + .unwrap() + .into(); + + let serialized = serde_json::to_string(&parsed).unwrap(); + + // The serialized JSON should not contain "deny":null + let reparsed: RawTurboJson = RawRootTurboJson::parse(&serialized, "turbo.json") + .unwrap() + .into(); + + // Verify the structure is preserved + assert!(reparsed.boundaries.is_some()); + let boundaries = reparsed.boundaries.as_ref().unwrap(); + assert!(boundaries.dependencies.is_some()); + let deps = boundaries.dependencies.as_ref().unwrap(); + assert!(deps.allow.is_some()); + assert!(deps.deny.is_none()); // This should be None, not null + } + + #[test] + fn test_prune_tasks_preserves_boundaries_structure() { + let json_with_boundaries = r#"{ + "tasks": { + "build": {}, + "app-a#build": {} + }, + "boundaries": { + "dependencies": { + "allow": [] + } + } + }"#; + + let parsed: RawTurboJson = RawRootTurboJson::parse(json_with_boundaries, "turbo.json") + .unwrap() + .into(); + + // Simulate the prune operation + let pruned = parsed.prune_tasks(&["app-a"]); + + // Serialize the pruned config + let serialized = serde_json::to_string_pretty(&pruned).unwrap(); + + // Parse the serialized config to ensure it's valid + let reparsed_result = RawRootTurboJson::parse(&serialized, "turbo.json"); + assert!( + reparsed_result.is_ok(), + "Failed to parse pruned config: {:?}", + reparsed_result.err() + ); + + let reparsed: RawTurboJson = reparsed_result.unwrap().into(); + + // Verify boundaries structure is preserved + assert!(reparsed.boundaries.is_some()); + let boundaries = reparsed.boundaries.as_ref().unwrap(); + assert!(boundaries.dependencies.is_some()); + let deps = boundaries.dependencies.as_ref().unwrap(); + assert!(deps.allow.is_some()); + assert!(deps.deny.is_none()); // This should be None, not serialized as + // null + } } diff --git a/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__package_rule.snap b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__package_rule.snap index 916a08b05388f..95dfba3660f99 100644 --- a/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__package_rule.snap +++ b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__package_rule.snap @@ -6,7 +6,6 @@ expression: raw_boundaries_config "dependencies": { "allow": [ "my-package" - ], - "deny": null + ] } } diff --git a/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__tags_and_dependencies.snap b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__tags_and_dependencies.snap index 0bc9b31481351..1e5d9c448becb 100644 --- a/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__tags_and_dependencies.snap +++ b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__tags_and_dependencies.snap @@ -8,8 +8,7 @@ expression: raw_boundaries_config "dependencies": { "allow": [ "my-package" - ], - "deny": null + ] } } }