Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.4.1] - 2026-04-09

### Fixed

- Config commands now execute in declaration order instead of alphabetical key order (#39)

## [0.4.0] - 2026-04-08

### Added
Expand Down Expand Up @@ -55,7 +61,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Docs deploy workflow triggers and Node version
- Result marker parser handling of embedded markers

[Unreleased]: https://github.com/codesoda/bugatti-cli/compare/v0.4.0...HEAD
[Unreleased]: https://github.com/codesoda/bugatti-cli/compare/v0.4.1...HEAD
[0.4.1]: https://github.com/codesoda/bugatti-cli/compare/v0.4.0...v0.4.1
[0.4.0]: https://github.com/codesoda/bugatti-cli/compare/v0.3.1...v0.4.0
[0.3.1]: https://github.com/codesoda/bugatti-cli/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/codesoda/bugatti-cli/releases/tag/v0.3.0
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bugatti"
version = "0.4.0"
version = "0.4.1"
edition = "2021"
description = "A CLI for plain-English, agent-assisted local application verification using *.test.toml files"

Expand All @@ -9,6 +9,7 @@ chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4", features = ["derive"] }
ctrlc = "3"
glob = "0.3.3"
indexmap = { version = "2", features = ["serde"] }
libc = "0.2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down
4 changes: 2 additions & 2 deletions src/claude_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ impl<'a> Iterator for StreamTurnIterator<'a> {
mod tests {
use super::*;
use crate::config::{Config, ProviderConfig};
use std::collections::BTreeMap;
use indexmap::IndexMap;

fn test_config() -> Config {
Config {
Expand All @@ -547,7 +547,7 @@ mod tests {
strict_warnings: None,
base_url: None,
},
commands: BTreeMap::new(),
commands: IndexMap::new(),
checkpoint: None,
}
}
Expand Down
29 changes: 25 additions & 4 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ pub fn validate_skip_readiness(

/// Execute all short-lived commands from the config during the setup phase.
///
/// Commands are executed in BTreeMap order (alphabetical by name).
/// Commands are executed in declaration order (the order they appear in bugatti.config.toml).
/// stdout and stderr are captured and stored under the run's logs/ directory.
/// If any command exits non-zero, execution stops and an error is returned.
///
Expand Down Expand Up @@ -615,7 +615,7 @@ mod tests {
use super::*;
use crate::config::{CommandDef, CommandKind, Config, ProviderConfig};
use crate::run::{ArtifactDir, RunId};
use std::collections::BTreeMap;
use indexmap::IndexMap;

fn make_config(commands: Vec<(&str, CommandKind, &str)>) -> Config {
make_config_with_readiness(
Expand All @@ -629,7 +629,7 @@ mod tests {
fn make_config_with_readiness(
commands: Vec<(&str, CommandKind, &str, Option<&str>)>,
) -> Config {
let mut map = BTreeMap::new();
let mut map = IndexMap::new();
for (name, kind, cmd, readiness_url) in commands {
map.insert(
name.to_string(),
Expand Down Expand Up @@ -758,7 +758,7 @@ mod tests {
let artifact_dir = ArtifactDir::from_run_id(tmp.path(), &run_id);
artifact_dir.create_all().unwrap();

// BTreeMap ordering: "a_first" comes before "b_second"
// Insertion ordering: "a_first" was inserted before "b_second"
let config = make_config(vec![
("a_first", CommandKind::ShortLived, "exit 1"),
("b_second", CommandKind::ShortLived, "echo should_not_run"),
Expand Down Expand Up @@ -991,4 +991,25 @@ mod tests {

teardown_processes(&mut tracked);
}

#[test]
fn commands_execute_in_declaration_order() {
let tmp = tempfile::tempdir().unwrap();
let run_id = RunId("test-run".to_string());
let artifact_dir = ArtifactDir::from_run_id(tmp.path(), &run_id);
artifact_dir.create_all().unwrap();

// Insert in reverse-alpha order: z_last first, a_first second.
// With BTreeMap this would have executed a_first then z_last.
// With IndexMap it must execute z_last then a_first.
let config = make_config(vec![
("z_last", CommandKind::ShortLived, "echo z_last"),
("a_first", CommandKind::ShortLived, "echo a_first"),
]);

let results = run_short_lived_commands(&config, &artifact_dir, &[]).unwrap();
assert_eq!(results.len(), 2);
assert_eq!(results[0].name, "z_last");
assert_eq!(results[1].name, "a_first");
}
}
43 changes: 33 additions & 10 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::test_file::ProviderOverrides;
use indexmap::IndexMap;
use serde::Deserialize;
use std::collections::BTreeMap;
use std::path::Path;

/// Top-level project configuration loaded from bugatti.config.toml.
Expand All @@ -10,7 +10,7 @@ pub struct Config {
#[serde(default)]
pub provider: ProviderConfig,
#[serde(default)]
pub commands: BTreeMap<String, CommandDef>,
pub commands: IndexMap<String, CommandDef>,
#[serde(default)]
pub checkpoint: Option<CheckpointConfig>,
}
Expand Down Expand Up @@ -196,6 +196,7 @@ pub fn load_config(dir: &Path) -> Result<Config, ConfigError> {
mod tests {
use super::*;
use crate::test_file::{ProviderOverrides, Step, TestFile, TestOverrides};
use indexmap::IndexMap;
use std::fs;

#[test]
Expand Down Expand Up @@ -243,6 +244,28 @@ readiness_url = "http://localhost:3000/health"
);
}

#[test]
fn config_preserves_toml_declaration_order() {
let dir = tempfile::tempdir().unwrap();
fs::write(
dir.path().join("bugatti.config.toml"),
r#"
[commands.z_server]
kind = "long_lived"
cmd = "sleep 60"

[commands.a_migrate]
kind = "short_lived"
cmd = "echo migrate"
"#,
)
.unwrap();

let config = load_config(dir.path()).unwrap();
let names: Vec<&String> = config.commands.keys().collect();
assert_eq!(names, vec!["z_server", "a_migrate"]);
}

#[test]
fn missing_config_returns_defaults() {
let dir = tempfile::tempdir().unwrap();
Expand Down Expand Up @@ -278,7 +301,7 @@ readiness_url = "http://localhost:3000/health"
strict_warnings: None,
base_url: None,
},
commands: BTreeMap::new(),
commands: IndexMap::new(),
checkpoint: None,
};
let test_file = TestFile {
Expand Down Expand Up @@ -321,7 +344,7 @@ readiness_url = "http://localhost:3000/health"
strict_warnings: None,
base_url: None,
},
commands: BTreeMap::new(),
commands: IndexMap::new(),
checkpoint: None,
};
let test_file = TestFile {
Expand Down Expand Up @@ -360,7 +383,7 @@ readiness_url = "http://localhost:3000/health"
strict_warnings: None,
base_url: None,
},
commands: BTreeMap::new(),
commands: IndexMap::new(),
checkpoint: None,
};
let test_file = TestFile {
Expand Down Expand Up @@ -419,7 +442,7 @@ step_timeout_secs = 600
step_timeout_secs: Some(300),
..ProviderConfig::default()
},
commands: BTreeMap::new(),
commands: IndexMap::new(),
checkpoint: None,
};
let test_file = TestFile {
Expand All @@ -445,7 +468,7 @@ step_timeout_secs = 600
step_timeout_secs: Some(300),
..ProviderConfig::default()
},
commands: BTreeMap::new(),
commands: IndexMap::new(),
checkpoint: None,
};
let test_file = TestFile {
Expand Down Expand Up @@ -487,7 +510,7 @@ strict_warnings = true
strict_warnings: Some(true),
..ProviderConfig::default()
},
commands: BTreeMap::new(),
commands: IndexMap::new(),
checkpoint: None,
};
let test_file = TestFile {
Expand Down Expand Up @@ -533,7 +556,7 @@ base_url = "http://localhost:3000"
base_url: Some("http://localhost:3000".to_string()),
..ProviderConfig::default()
},
commands: BTreeMap::new(),
commands: IndexMap::new(),
checkpoint: None,
};
let test_file = TestFile {
Expand Down Expand Up @@ -562,7 +585,7 @@ base_url = "http://localhost:3000"
base_url: Some("http://localhost:3000".to_string()),
..ProviderConfig::default()
},
commands: BTreeMap::new(),
commands: IndexMap::new(),
checkpoint: None,
};
let test_file = TestFile {
Expand Down
4 changes: 2 additions & 2 deletions src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,10 @@ pub fn initialize_run(
mod tests {
use super::*;
use crate::config::{CommandDef, CommandKind, Config, ProviderConfig};
use std::collections::BTreeMap;
use indexmap::IndexMap;

fn test_config() -> Config {
let mut commands = BTreeMap::new();
let mut commands = IndexMap::new();
commands.insert(
"migrate".to_string(),
CommandDef {
Expand Down
Loading