use tokio_stream::StreamExt;
use trusty_mpm::core::provisioning_stage::ProvisioningStage;
struct StageEnvelope {
repo_url: String,
stage: String,
label: String,
}
fn parse_stage_frame(frame: &str) -> Option<StageEnvelope> {
let data: String = frame
.lines()
.filter_map(|line| line.strip_prefix("data:"))
.map(str::trim)
.collect::<Vec<_>>()
.join("\n");
if data.is_empty() {
return None;
}
let value: serde_json::Value = serde_json::from_str(&data).ok()?;
if value.get("kind").and_then(serde_json::Value::as_str) != Some("provisioning_stage") {
return None;
}
Some(StageEnvelope {
repo_url: value.get("repo_url")?.as_str()?.to_string(),
stage: value.get("stage")?.as_str()?.to_string(),
label: value.get("label")?.as_str()?.to_string(),
})
}
pub(crate) async fn watch_provisioning_stages(
client: reqwest::Client,
events_url: String,
repo_url: String,
spinner: trusty_progress::ProgressHandle,
) {
let Ok(resp) = client.get(&events_url).send().await else {
return;
};
if !resp.status().is_success() {
return;
}
let mut stream = resp.bytes_stream();
let mut buf = String::new();
while let Some(Ok(chunk)) = stream.next().await {
buf.push_str(&String::from_utf8_lossy(&chunk));
while let Some(idx) = buf.find("\n\n") {
let frame: String = buf.drain(..idx + 2).collect();
let Some(envelope) = parse_stage_frame(&frame) else {
continue;
};
if envelope.repo_url != repo_url {
continue;
}
spinner.set_message(format!("tm: {}…", envelope.label));
if envelope.stage == ProvisioningStage::Complete.wire_name() {
return;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_stage_frame_extracts_matching_repo_url() {
let frame = "data: {\"kind\":\"provisioning_stage\",\"session\":\"s1\",\"repo_url\":\"https://github.com/acme/widgets\",\"stage\":\"CloningRepo\",\"label\":\"Cloning repository\",\"at\":\"2026-01-01T00:00:00Z\"}\n\n";
let env = parse_stage_frame(frame).expect("parses");
assert_eq!(env.repo_url, "https://github.com/acme/widgets");
assert_eq!(env.stage, "CloningRepo");
assert_eq!(env.label, "Cloning repository");
}
#[test]
fn parse_stage_frame_ignores_other_repo_url() {
let frame = "data: {\"kind\":\"provisioning_stage\",\"session\":\"s1\",\"repo_url\":\"https://github.com/other/repo\",\"stage\":\"CloningRepo\",\"label\":\"Cloning repository\",\"at\":\"2026-01-01T00:00:00Z\"}\n\n";
let env = parse_stage_frame(frame).expect("parses");
assert_eq!(env.repo_url, "https://github.com/other/repo");
}
#[test]
fn parse_stage_frame_ignores_keep_alive_comment() {
assert!(parse_stage_frame(": ping\n\n").is_none());
}
#[test]
fn parse_stage_frame_ignores_non_stage_events() {
let frame = "data: {\"session\":\"s1\",\"event\":\"PreToolUse\",\"at\":\"2026-01-01T00:00:00Z\",\"payload\":{}}\n\n";
assert!(parse_stage_frame(frame).is_none());
assert!(parse_stage_frame("data: not json at all\n\n").is_none());
assert!(parse_stage_frame("\n\n").is_none());
}
}