wrkflw_executor/
environment.rs1use chrono::Utc;
2use serde_yaml::Value;
3use std::{collections::HashMap, fs, io, path::Path};
4use wrkflw_matrix::MatrixCombination;
5use wrkflw_parser::workflow::WorkflowDefinition;
6
7pub fn setup_github_environment_files(workspace_dir: &Path) -> io::Result<()> {
8 let github_dir = workspace_dir.join("github");
10 fs::create_dir_all(&github_dir)?;
11
12 let github_output = github_dir.join("output");
14 let github_env = github_dir.join("env");
15 let github_path = github_dir.join("path");
16 let github_step_summary = github_dir.join("step_summary");
17
18 fs::write(&github_output, "")?;
20 fs::write(&github_env, "")?;
21 fs::write(&github_path, "")?;
22 fs::write(&github_step_summary, "")?;
23
24 Ok(())
25}
26
27pub fn create_github_context(
28 workflow: &WorkflowDefinition,
29 workspace_dir: &Path,
30) -> HashMap<String, String> {
31 let mut env = HashMap::new();
32
33 env.insert("GITHUB_WORKFLOW".to_string(), workflow.name.clone());
35 env.insert("GITHUB_ACTION".to_string(), "run".to_string());
36 env.insert("GITHUB_ACTOR".to_string(), "wrkflw".to_string());
37 env.insert("GITHUB_REPOSITORY".to_string(), get_repo_name());
38 env.insert("GITHUB_EVENT_NAME".to_string(), get_event_name(workflow));
39 env.insert("GITHUB_WORKSPACE".to_string(), get_workspace_path());
40 env.insert("GITHUB_SHA".to_string(), get_current_sha());
41 env.insert("GITHUB_REF".to_string(), get_current_ref());
42
43 env.insert(
45 "GITHUB_OUTPUT".to_string(),
46 workspace_dir
47 .join("github")
48 .join("output")
49 .to_string_lossy()
50 .to_string(),
51 );
52 env.insert(
53 "GITHUB_ENV".to_string(),
54 workspace_dir
55 .join("github")
56 .join("env")
57 .to_string_lossy()
58 .to_string(),
59 );
60 env.insert(
61 "GITHUB_PATH".to_string(),
62 workspace_dir
63 .join("github")
64 .join("path")
65 .to_string_lossy()
66 .to_string(),
67 );
68 env.insert(
69 "GITHUB_STEP_SUMMARY".to_string(),
70 workspace_dir
71 .join("github")
72 .join("step_summary")
73 .to_string_lossy()
74 .to_string(),
75 );
76
77 let now = Utc::now();
79 env.insert("GITHUB_RUN_ID".to_string(), format!("{}", now.timestamp()));
80 env.insert("GITHUB_RUN_NUMBER".to_string(), "1".to_string());
81
82 env.insert("RUNNER_TEMP".to_string(), get_temp_dir());
84 env.insert("RUNNER_TOOL_CACHE".to_string(), get_tool_cache_dir());
85
86 env
87}
88
89pub fn add_matrix_context(
91 env: &mut HashMap<String, String>,
92 matrix_combination: &MatrixCombination,
93) {
94 for (key, value) in &matrix_combination.values {
96 let env_key = format!("MATRIX_{}", key.to_uppercase());
97 let env_value = value_to_string(value);
98 env.insert(env_key, env_value);
99 }
100
101 if let Ok(json_value) = serde_json::to_string(&matrix_combination.values) {
103 env.insert("MATRIX_CONTEXT".to_string(), json_value);
104 }
105}
106
107fn value_to_string(value: &Value) -> String {
109 match value {
110 Value::String(s) => s.clone(),
111 Value::Number(n) => n.to_string(),
112 Value::Bool(b) => b.to_string(),
113 Value::Sequence(seq) => {
114 let items = seq
115 .iter()
116 .map(value_to_string)
117 .collect::<Vec<_>>()
118 .join(",");
119 items
120 }
121 Value::Mapping(map) => {
122 let items = map
123 .iter()
124 .map(|(k, v)| format!("{}={}", value_to_string(k), value_to_string(v)))
125 .collect::<Vec<_>>()
126 .join(",");
127 items
128 }
129 Value::Null => "".to_string(),
130 _ => "".to_string(),
131 }
132}
133
134fn get_repo_name() -> String {
135 if let Ok(output) = std::process::Command::new("git")
137 .args(["remote", "get-url", "origin"])
138 .output()
139 {
140 if output.status.success() {
141 let url = String::from_utf8_lossy(&output.stdout);
142 if let Some(repo) = extract_repo_from_url(&url) {
143 return repo;
144 }
145 }
146 }
147
148 let current_dir = std::env::current_dir().unwrap_or_default();
150 format!(
151 "wrkflw/{}",
152 current_dir
153 .file_name()
154 .unwrap_or_default()
155 .to_string_lossy()
156 )
157}
158
159fn extract_repo_from_url(url: &str) -> Option<String> {
160 let url = url.trim();
162
163 if url.starts_with("git@") {
165 let parts: Vec<&str> = url.split(':').collect();
166 if parts.len() == 2 {
167 let repo_part = parts[1].trim_end_matches(".git");
168 return Some(repo_part.to_string());
169 }
170 }
171
172 if url.starts_with("http") {
174 let without_protocol = url.split("://").nth(1)?;
175 let parts: Vec<&str> = without_protocol.split('/').collect();
176 if parts.len() >= 3 {
177 let owner = parts[1];
178 let repo = parts[2].trim_end_matches(".git");
179 return Some(format!("{}/{}", owner, repo));
180 }
181 }
182
183 None
184}
185
186fn get_event_name(workflow: &WorkflowDefinition) -> String {
187 if let Some(first_trigger) = workflow.on.first() {
189 return first_trigger.clone();
190 }
191 "workflow_dispatch".to_string()
192}
193
194fn get_workspace_path() -> String {
195 std::env::current_dir()
196 .unwrap_or_default()
197 .to_string_lossy()
198 .to_string()
199}
200
201fn get_current_sha() -> String {
202 if let Ok(output) = std::process::Command::new("git")
203 .args(["rev-parse", "HEAD"])
204 .output()
205 {
206 if output.status.success() {
207 return String::from_utf8_lossy(&output.stdout).trim().to_string();
208 }
209 }
210
211 "0000000000000000000000000000000000000000".to_string()
212}
213
214fn get_current_ref() -> String {
215 if let Ok(output) = std::process::Command::new("git")
216 .args(["symbolic-ref", "--short", "HEAD"])
217 .output()
218 {
219 if output.status.success() {
220 return format!(
221 "refs/heads/{}",
222 String::from_utf8_lossy(&output.stdout).trim()
223 );
224 }
225 }
226
227 "refs/heads/main".to_string()
228}
229
230fn get_temp_dir() -> String {
231 let temp_dir = std::env::temp_dir();
232 temp_dir.join("wrkflw").to_string_lossy().to_string()
233}
234
235fn get_tool_cache_dir() -> String {
236 let home_dir = dirs::home_dir().unwrap_or_default();
237 home_dir
238 .join(".wrkflw")
239 .join("tools")
240 .to_string_lossy()
241 .to_string()
242}