mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-11 22:53:00 +00:00
fabro(01KKSDEQC3GZ6NB715GFVPPRG6): implement (success)
Fabro-Run: 01KKSDEQC3GZ6NB715GFVPPRG6
Fabro-Completed: 5
Fabro-Checkpoint: d4b68fa788
⚒️ Generated with [Fabro](https://fabro.sh)
This commit is contained in:
parent
68b15d3ead
commit
ed8d88441b
3 changed files with 96 additions and 2 deletions
|
|
@ -203,13 +203,28 @@ pub fn format_event_pretty(line: &str, styles: &fabro_util::terminal::Styles) ->
|
|||
"WorkflowRunStarted" => {
|
||||
let name = str_field(&envelope, "workflow_name").unwrap_or("?");
|
||||
let run_id = str_field(&envelope, "run_id").unwrap_or("?");
|
||||
Some(format!(
|
||||
let header = format!(
|
||||
"{} {} {} {}",
|
||||
styles.dim.apply_to(&ts),
|
||||
styles.bold_cyan.apply_to("\u{25b6}"),
|
||||
styles.bold.apply_to(name),
|
||||
styles.dim.apply_to(run_id),
|
||||
))
|
||||
);
|
||||
match str_field(&envelope, "goal") {
|
||||
Some(goal) if !goal.is_empty() => {
|
||||
let indent = " ";
|
||||
let term_width = fabro_util::terminal::Styles::terminal_width();
|
||||
let wrap_width = term_width.saturating_sub(indent.len());
|
||||
let rendered = styles.render_markdown_width(goal, wrap_width);
|
||||
let body: String = rendered
|
||||
.lines()
|
||||
.map(|l| format!("{indent}{l}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
Some(format!("{header}\n{body}\n"))
|
||||
}
|
||||
_ => Some(header),
|
||||
}
|
||||
}
|
||||
|
||||
"WorkflowRunCompleted" => {
|
||||
|
|
@ -700,6 +715,26 @@ mod tests {
|
|||
let result = format_event_pretty(line, &styles).unwrap();
|
||||
assert!(result.contains("smoke"), "got: {result}");
|
||||
assert!(result.contains("abc123"), "got: {result}");
|
||||
// No goal — should be a single line (no newline-separated body)
|
||||
assert!(
|
||||
!result.contains('\n'),
|
||||
"no goal means single line, got: {result}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pretty_workflow_run_started_with_goal() {
|
||||
let styles = no_color_styles();
|
||||
let line = r#"{"ts":"2026-01-01T14:23:01Z","run_id":"abc123","event":"WorkflowRunStarted","workflow_name":"smoke","goal":"Fix the bug"}"#;
|
||||
let result = format_event_pretty(line, &styles).unwrap();
|
||||
assert!(result.contains("smoke"), "got: {result}");
|
||||
assert!(result.contains("abc123"), "got: {result}");
|
||||
assert!(result.contains("Fix the bug"), "got: {result}");
|
||||
// Goal should be on a separate indented line
|
||||
assert!(
|
||||
result.contains('\n'),
|
||||
"goal should produce multi-line output, got: {result}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -1216,6 +1216,14 @@ impl WorkflowRunEngine {
|
|||
} else {
|
||||
None
|
||||
},
|
||||
goal: {
|
||||
let g = graph.goal();
|
||||
if g.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(g.to_string())
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Resolve work_dir from config for hooks
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ pub enum WorkflowRunEvent {
|
|||
run_branch: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
worktree_dir: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
goal: Option<String>,
|
||||
},
|
||||
WorkflowRunCompleted {
|
||||
duration_ms: u64,
|
||||
|
|
@ -1013,6 +1015,7 @@ mod tests {
|
|||
base_sha: None,
|
||||
run_branch: None,
|
||||
worktree_dir: None,
|
||||
goal: None,
|
||||
});
|
||||
let events = received.lock().unwrap();
|
||||
assert_eq!(events.len(), 1);
|
||||
|
|
@ -1510,6 +1513,7 @@ mod tests {
|
|||
base_sha: None,
|
||||
run_branch: None,
|
||||
worktree_dir: None,
|
||||
goal: None,
|
||||
});
|
||||
assert!(emitter.last_event_at() > 0);
|
||||
}
|
||||
|
|
@ -1679,6 +1683,7 @@ mod tests {
|
|||
base_sha: None,
|
||||
run_branch: None,
|
||||
worktree_dir: None,
|
||||
goal: None,
|
||||
};
|
||||
let (name, fields) = flatten_event(&event);
|
||||
assert_eq!(name, "WorkflowRunStarted");
|
||||
|
|
@ -2060,4 +2065,50 @@ mod tests {
|
|||
assert_eq!(fields["command_index"], 1);
|
||||
assert!(!fields.contains_key("index"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workflow_run_started_with_goal_roundtrip() {
|
||||
let event = WorkflowRunEvent::WorkflowRunStarted {
|
||||
name: "deploy".to_string(),
|
||||
run_id: "r42".to_string(),
|
||||
base_sha: None,
|
||||
run_branch: None,
|
||||
worktree_dir: None,
|
||||
goal: Some("Fix the bug".to_string()),
|
||||
};
|
||||
let json = serde_json::to_string(&event).unwrap();
|
||||
assert!(json.contains("\"goal\":\"Fix the bug\""));
|
||||
let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap();
|
||||
assert!(
|
||||
matches!(deserialized, WorkflowRunEvent::WorkflowRunStarted { goal: Some(g), .. } if g == "Fix the bug")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workflow_run_started_without_goal_backward_compat() {
|
||||
// Old JSONL without `goal` field should deserialize with goal: None
|
||||
let json = r#"{"WorkflowRunStarted":{"name":"smoke","run_id":"r1"}}"#;
|
||||
let deserialized: WorkflowRunEvent = serde_json::from_str(json).unwrap();
|
||||
assert!(matches!(
|
||||
deserialized,
|
||||
WorkflowRunEvent::WorkflowRunStarted { goal: None, .. }
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workflow_run_started_none_goal_omitted_from_json() {
|
||||
let event = WorkflowRunEvent::WorkflowRunStarted {
|
||||
name: "ci".to_string(),
|
||||
run_id: "r2".to_string(),
|
||||
base_sha: None,
|
||||
run_branch: None,
|
||||
worktree_dir: None,
|
||||
goal: None,
|
||||
};
|
||||
let json = serde_json::to_string(&event).unwrap();
|
||||
assert!(
|
||||
!json.contains("goal"),
|
||||
"goal should be omitted when None: {json}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue