huskies: merge 1177 bug project.toml scaffold puts top-level keys after [[component]] so uncommenting them silently no-ops
This commit is contained in:
@@ -246,7 +246,13 @@ fn default_max_mesh_peers() -> usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration for a project component (name, path, setup/teardown commands).
|
/// Configuration for a project component (name, path, setup/teardown commands).
|
||||||
|
///
|
||||||
|
/// `deny_unknown_fields` turns a top-level setting that lands inside a
|
||||||
|
/// `[[component]]` table (e.g. because it was placed below the last
|
||||||
|
/// `[[component]]` header) into a hard parse error instead of a silently
|
||||||
|
/// dropped key.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct ComponentConfig {
|
pub struct ComponentConfig {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|||||||
@@ -521,6 +521,26 @@ runtime = "openai"
|
|||||||
assert!(err.contains("unknown runtime 'openai'"));
|
assert!(err.contains("unknown runtime 'openai'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unrecognized_key_in_component_table_errors_instead_of_silently_dropping() {
|
||||||
|
// Regression for story 1177: a top-level setting that ends up attached to
|
||||||
|
// a `[[component]]` table (e.g. because it was placed below the last
|
||||||
|
// `[[component]]` header) must be a hard error, not a silently swallowed
|
||||||
|
// key.
|
||||||
|
let toml_str = r#"
|
||||||
|
[[component]]
|
||||||
|
name = "server"
|
||||||
|
path = "."
|
||||||
|
base_branch = "main"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let err = ProjectConfig::parse(toml_str).unwrap_err();
|
||||||
|
assert!(
|
||||||
|
err.contains("base_branch"),
|
||||||
|
"expected error to mention the unrecognized key, got: {err}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ── base_branch config ──────────────────────────────────────────────────
|
// ── base_branch config ──────────────────────────────────────────────────
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -8,8 +8,13 @@ use super::detect::detect_components_toml;
|
|||||||
use super::templates::{DEFAULT_AGENTS_TOML, DEFAULT_PROJECT_SETTINGS_TOML};
|
use super::templates::{DEFAULT_AGENTS_TOML, DEFAULT_PROJECT_SETTINGS_TOML};
|
||||||
|
|
||||||
pub(super) fn generate_project_toml(root: &Path) -> String {
|
pub(super) fn generate_project_toml(root: &Path) -> String {
|
||||||
|
// TOML requires bare `key = value` settings to appear before any table
|
||||||
|
// header. Emitting components (which open with `[[component]]`) first
|
||||||
|
// would make every commented top-level setting below them silently
|
||||||
|
// attach to the last `[[component]]` table once uncommented, instead of
|
||||||
|
// the top-level document — so settings must come first.
|
||||||
let components = detect_components_toml(root);
|
let components = detect_components_toml(root);
|
||||||
format!("{components}\n{DEFAULT_PROJECT_SETTINGS_TOML}")
|
format!("{DEFAULT_PROJECT_SETTINGS_TOML}\n{components}")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn write_file_if_missing(path: &Path, content: &str) -> Result<(), String> {
|
pub(super) fn write_file_if_missing(path: &Path, content: &str) -> Result<(), String> {
|
||||||
|
|||||||
@@ -140,6 +140,38 @@ fn scaffold_project_toml_round_trips_through_project_config_load() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scaffold_project_toml_uncommenting_base_branch_takes_effect() {
|
||||||
|
use crate::config::ProjectConfig;
|
||||||
|
|
||||||
|
// Regression for story 1177: the scaffold template used to emit
|
||||||
|
// `[[component]]` headers before the commented top-level settings, so
|
||||||
|
// uncommenting a setting like `base_branch` attached it to the last
|
||||||
|
// `[[component]]` table instead of the top level, silently no-opping.
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
fs::write(
|
||||||
|
dir.path().join("Cargo.toml"),
|
||||||
|
"[package]\nname = \"myapp\"\n",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
scaffold_story_kit(dir.path(), 3001).unwrap();
|
||||||
|
|
||||||
|
let content = fs::read_to_string(dir.path().join(".huskies/project.toml")).unwrap();
|
||||||
|
assert!(
|
||||||
|
content.contains("# base_branch = \"main\""),
|
||||||
|
"scaffold should emit a commented-out base_branch line to uncomment"
|
||||||
|
);
|
||||||
|
let uncommented = content.replace("# base_branch = \"main\"", "base_branch = \"main\"");
|
||||||
|
|
||||||
|
let config =
|
||||||
|
ProjectConfig::parse(&uncommented).expect("uncommented project.toml should still parse");
|
||||||
|
assert_eq!(
|
||||||
|
config.base_branch,
|
||||||
|
Some("main".to_string()),
|
||||||
|
"uncommenting base_branch in the scaffolded project.toml must take effect"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scaffold_context_is_blank_template_not_story_kit_content() {
|
fn scaffold_context_is_blank_template_not_story_kit_content() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user