1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
mod evaluator;
mod executor;
mod logging;
mod matrix;
mod models;
mod parser;
mod runtime;
mod ui;
mod utils;
mod validators;
mod matrix_test;
use bollard::Docker;
use clap::{Parser, Subcommand};
use std::path::PathBuf;
#[derive(Debug, Parser)]
#[command(
name = "wrkflw",
about = "GitHub Workflow validator and executor",
version
)]
struct Wrkflw {
#[command(subcommand)]
command: Option<Commands>,
/// Run in verbose mode with detailed output
#[arg(short, long, global = true)]
verbose: bool,
}
#[derive(Debug, Subcommand)]
enum Commands {
/// Validate GitHub workflow files
Validate {
/// Path to workflow file or directory (defaults to .github/workflows)
path: Option<PathBuf>,
},
/// Execute GitHub workflow files locally
Run {
/// Path to workflow file to execute
path: PathBuf,
/// Use emulation mode instead of Docker
#[arg(short, long)]
emulate: bool,
},
/// Open TUI interface to manage workflows
Tui {
/// Path to workflow file or directory (defaults to .github/workflows)
path: Option<PathBuf>,
/// Use emulation mode instead of Docker
#[arg(short, long)]
emulate: bool,
},
}
async fn cleanup_on_exit() {
match Docker::connect_with_local_defaults() {
Ok(docker) => {
executor::cleanup_containers(&docker).await;
}
Err(_) => {
// Docker not available, nothing to clean up
}
}
}
async fn handle_signals() {
// Wait for Ctrl+C
tokio::signal::ctrl_c()
.await
.expect("Failed to listen for ctrl+c event");
println!("Received Ctrl+C, shutting down and cleaning up...");
// Clean up containers
cleanup_on_exit().await;
// Exit with success status
std::process::exit(0);
}
#[tokio::main]
async fn main() {
let cli = Wrkflw::parse();
let verbose = cli.verbose;
// Setup a Ctrl+C handler that runs in the background
tokio::spawn(handle_signals());
match &cli.command {
Some(Commands::Validate { path }) => {
// Determine the path to validate
let validate_path = path
.clone()
.unwrap_or_else(|| PathBuf::from(".github/workflows"));
// Run the validation
ui::validate_workflow(&validate_path, verbose).unwrap_or_else(|e| {
eprintln!("Error: {}", e);
std::process::exit(1);
});
}
Some(Commands::Run { path, emulate }) => {
// Run the workflow execution
let runtime_type = if *emulate {
executor::RuntimeType::Emulation
} else {
executor::RuntimeType::Docker
};
// Run in TUI mode with the specific workflow
match ui::run_wrkflw_tui(Some(path), runtime_type, verbose).await {
Ok(_) => {
// Clean up on successful exit
cleanup_on_exit().await;
}
Err(e) => {
eprintln!("Error: {}", e);
cleanup_on_exit().await;
std::process::exit(1);
}
}
}
Some(Commands::Tui { path, emulate }) => {
// Open the TUI interface
let runtime_type = if *emulate {
executor::RuntimeType::Emulation
} else {
executor::RuntimeType::Docker
};
match ui::run_wrkflw_tui(path.as_ref(), runtime_type, verbose).await {
Ok(_) => {
// Clean up on successful exit
cleanup_on_exit().await;
}
Err(e) => {
eprintln!("Error: {}", e);
cleanup_on_exit().await;
std::process::exit(1);
}
}
}
None => {
// Default to TUI interface if no subcommand
match ui::run_wrkflw_tui(
Some(&PathBuf::from(".github/workflows")),
executor::RuntimeType::Docker,
verbose,
)
.await
{
Ok(_) => {
// Clean up on successful exit
cleanup_on_exit().await;
}
Err(e) => {
eprintln!("Error: {}", e);
cleanup_on_exit().await;
std::process::exit(1);
}
}
}
}
// Final cleanup before program exit
cleanup_on_exit().await;
}