1pub mod ConfigLoader;
12pub mod ScriptRunner;
13pub mod EnvironmentResolver;
14
15use rhai::Engine;
16
17pub fn create_engine() -> Engine {
24 let mut engine = Engine::new();
25
26 engine.set_max_expr_depths(0, 0);
28 engine.set_max_operations(0);
29 engine.set_allow_shadowing(true);
30
31 register_utility_functions(&mut engine);
33
34 engine
35}
36
37fn register_utility_functions(engine:&mut Engine) {
39 engine.register_fn("get_os_type", || std::env::consts::OS.to_string());
41 engine.register_fn("get_arch", || std::env::consts::ARCH.to_string());
42 engine.register_fn("get_family", || std::env::consts::FAMILY.to_string());
43
44 engine.register_fn("get_env", |name:&str| -> String { std::env::var(name).unwrap_or_default() });
46
47 engine.register_fn("path_exists", |path:&str| -> bool { std::path::Path::new(path).exists() });
49
50 engine.register_fn("timestamp", || -> i64 {
52 std::time::SystemTime::now()
53 .duration_since(std::time::UNIX_EPOCH)
54 .unwrap_or_default()
55 .as_secs() as i64
56 });
57
58 engine.register_fn("print", |s:&str| {
60 println!("[Rhai] {}", s);
61 });
62}
63
64#[cfg(test)]
69mod tests {
70 use std::collections::HashMap;
71
72 use super::*;
73
74 fn get_expected_env_vars(profile_name:&str) -> Vec<(&'static str, &'static str)> {
76 match profile_name {
77 "debug" => {
78 vec![
79 ("Debug", "true"),
80 ("Browser", "true"),
81 ("Bundle", "true"),
82 ("Clean", "true"),
83 ("Compile", "false"),
84 ("NODE_ENV", "development"),
85 ("NODE_VERSION", "22"),
86 ("NODE_OPTIONS", "--max-old-space-size=16384"),
87 ("RUST_LOG", "debug"),
88 ("AIR_LOG_JSON", "false"),
89 ("AIR_LOG_FILE", ""),
90 ("Dependency", "Microsoft/VSCode"),
91 ]
92 },
93 "production" => {
94 vec![
95 ("Debug", "false"),
96 ("Browser", "false"),
97 ("Bundle", "true"),
98 ("Clean", "true"),
99 ("Compile", "true"),
100 ("NODE_ENV", "production"),
101 ("NODE_VERSION", "22"),
102 ("NODE_OPTIONS", "--max-old-space-size=8192"),
103 ("RUST_LOG", "info"),
104 ("AIR_LOG_JSON", "false"),
105 ("Dependency", "Microsoft/VSCode"),
106 ]
107 },
108 "release" => {
109 vec![
110 ("Debug", "false"),
111 ("Browser", "false"),
112 ("Bundle", "true"),
113 ("Clean", "true"),
114 ("Compile", "true"),
115 ("NODE_ENV", "production"),
116 ("NODE_VERSION", "22"),
117 ("NODE_OPTIONS", "--max-old-space-size=8192"),
118 ("RUST_LOG", "warn"),
119 ("AIR_LOG_JSON", "false"),
120 ("Dependency", "Microsoft/VSCode"),
121 ]
122 },
123 _ => vec![],
124 }
125 }
126
127 #[test]
128 fn test_config_loader_load() {
129 let result = ConfigLoader::load(".");
130
131 assert!(
132 result.is_ok(),
133 "ConfigLoader::load() should succeed but got error: {:?}",
134 result.err()
135 );
136
137 let config = result.unwrap();
138
139 assert_eq!(config.version, "1.0.0", "Configuration version should be 1.0.0");
140 assert!(!config.profiles.is_empty(), "Configuration should have at least one profile");
141 assert!(config.profiles.contains_key("debug"), "Debug profile should exist");
142 assert!(config.profiles.contains_key("production"), "Production profile should exist");
143 assert!(config.profiles.contains_key("release"), "Release profile should exist");
144 assert!(config.templates.is_some(), "Configuration should have templates defined");
145 }
146
147 #[test]
148 fn test_config_loader_get_profile_debug() {
149 let config = ConfigLoader::load(".").expect("Failed to load configuration");
150 let profile = ConfigLoader::get_profile(&config, "debug");
151
152 assert!(profile.is_some(), "Debug profile should exist in configuration");
153
154 let debug_profile = profile.unwrap();
155 assert!(debug_profile.description.is_some(), "Debug profile should have a description");
156 assert!(
157 debug_profile.env.is_some(),
158 "Debug profile should have environment variables defined"
159 );
160 assert!(
161 debug_profile.rhai_script.is_some(),
162 "Debug profile should have a Rhai script defined"
163 );
164
165 let debug_env = debug_profile.env.as_ref().unwrap();
166 assert_eq!(debug_env.get("Debug"), Some(&"true".to_string()));
167 assert_eq!(debug_env.get("NODE_ENV"), Some(&"development".to_string()));
168 assert_eq!(debug_env.get("RUST_LOG"), Some(&"debug".to_string()));
169 }
170
171 #[test]
172 fn test_resolve_profile_env_debug() {
173 let config = ConfigLoader::load(".").expect("Failed to load configuration");
174 let env_vars = ConfigLoader::resolve_profile_env(&config, "debug");
175
176 assert_eq!(env_vars.get("Debug"), Some(&"true".to_string()));
177 assert_eq!(env_vars.get("NODE_ENV"), Some(&"development".to_string()));
178 assert!(env_vars.contains_key("MOUNTAIN_DIR"), "Template variable should be present");
179 }
180
181 #[test]
182 fn test_execute_profile_script_debug() {
183 let config = ConfigLoader::load(".").expect("Failed to load configuration");
184 let profile = ConfigLoader::get_profile(&config, "debug").expect("Profile 'debug' not found");
185
186 let script_path = profile.rhai_script.as_ref().expect("No Rhai script defined for debug profile");
187 let full_script_path = std::path::Path::new(".").join(".vscode").join(script_path);
188
189 if !full_script_path.exists() {
190 panic!("Script file not found: {}", full_script_path.display());
191 }
192
193 let engine = create_engine();
194 let context = ScriptRunner::ScriptContext {
195 profile_name:"debug".to_string(),
196 cwd:".".to_string(),
197 manifest_dir:".".to_string(),
198 target_triple:None,
199 workbench_type:None,
200 features:HashMap::new(),
201 };
202
203 let result = ScriptRunner::ExecuteProfileScript(&engine, full_script_path.to_str().unwrap(), &context);
204
205 assert!(
206 result.is_ok(),
207 "Script execution should succeed but got error: {:?}",
208 result.err()
209 );
210
211 let script_result = result.unwrap();
212 assert!(script_result.success, "Script execution should report success");
213 assert!(script_result.error.is_none(), "Script execution should not have errors");
214 assert!(!script_result.env_vars.is_empty(), "Script should return environment variables");
215
216 let expected = get_expected_env_vars("debug");
217 for (key, expected_val) in expected {
218 let actual_val = script_result.env_vars.get(key).map(|s| s.as_str());
219 assert_eq!(
220 actual_val,
221 Some(expected_val),
222 "Env var '{}' should be '{}', got {:?}",
223 key,
224 expected_val,
225 actual_val
226 );
227 }
228 }
229
230 #[test]
231 fn test_execute_profile_script_production() {
232 let config = ConfigLoader::load(".").expect("Failed to load configuration");
233 let profile = ConfigLoader::get_profile(&config, "production").expect("Profile 'production' not found");
234
235 let script_path = profile
236 .rhai_script
237 .as_ref()
238 .expect("No Rhai script defined for production profile");
239 let full_script_path = std::path::Path::new(".").join(".vscode").join(script_path);
240
241 if !full_script_path.exists() {
242 panic!("Script file not found: {}", full_script_path.display());
243 }
244
245 let engine = create_engine();
246 let context = ScriptRunner::ScriptContext {
247 profile_name:"production".to_string(),
248 cwd:".".to_string(),
249 manifest_dir:".".to_string(),
250 target_triple:None,
251 workbench_type:None,
252 features:HashMap::new(),
253 };
254
255 let result = ScriptRunner::ExecuteProfileScript(&engine, full_script_path.to_str().unwrap(), &context);
256
257 assert!(
258 result.is_ok(),
259 "Script execution should succeed but got error: {:?}",
260 result.err()
261 );
262
263 let script_result = result.unwrap();
264 assert!(script_result.success, "Script execution should report success");
265 assert!(script_result.error.is_none(), "Script execution should not have errors");
266
267 let expected = get_expected_env_vars("production");
268 for (key, expected_val) in expected {
269 let actual_val = script_result.env_vars.get(key).map(|s| s.as_str());
270 assert_eq!(
271 actual_val,
272 Some(expected_val),
273 "Env var '{}' should be '{}', got {:?}",
274 key,
275 expected_val,
276 actual_val
277 );
278 }
279 }
280
281 #[test]
282 fn test_execute_profile_script_release() {
283 let config = ConfigLoader::load(".").expect("Failed to load configuration");
284 let profile = ConfigLoader::get_profile(&config, "release").expect("Profile 'release' not found");
285
286 let script_path = profile
287 .rhai_script
288 .as_ref()
289 .expect("No Rhai script defined for release profile");
290 let full_script_path = std::path::Path::new(".").join(".vscode").join(script_path);
291
292 if !full_script_path.exists() {
293 panic!("Script file not found: {}", full_script_path.display());
294 }
295
296 let engine = create_engine();
297 let context = ScriptRunner::ScriptContext {
298 profile_name:"release".to_string(),
299 cwd:".".to_string(),
300 manifest_dir:".".to_string(),
301 target_triple:None,
302 workbench_type:None,
303 features:HashMap::new(),
304 };
305
306 let result = ScriptRunner::ExecuteProfileScript(&engine, full_script_path.to_str().unwrap(), &context);
307
308 assert!(
309 result.is_ok(),
310 "Script execution should succeed but got error: {:?}",
311 result.err()
312 );
313
314 let script_result = result.unwrap();
315 assert!(script_result.success, "Script execution should report success");
316 assert!(script_result.error.is_none(), "Script execution should not have errors");
317
318 let expected = get_expected_env_vars("release");
319 for (key, expected_val) in expected {
320 let actual_val = script_result.env_vars.get(key).map(|s| s.as_str());
321 assert_eq!(
322 actual_val,
323 Some(expected_val),
324 "Env var '{}' should be '{}', got {:?}",
325 key,
326 expected_val,
327 actual_val
328 );
329 }
330 }
331
332 #[test]
333 fn test_execute_profile_script_bundler_preparation() {
334 let config = ConfigLoader::load(".").expect("Failed to load configuration");
335 let profile = ConfigLoader::get_profile(&config, "bundler-preparation")
336 .expect("Profile 'bundler-preparation' not found in configuration");
337
338 let script_path = profile
339 .rhai_script
340 .as_ref()
341 .expect("No Rhai script defined for bundler-preparation profile");
342 let full_script_path = std::path::Path::new(".").join(".vscode").join(script_path);
343
344 if !full_script_path.exists() {
345 eprintln!("Skipping test - script file not found: {}", full_script_path.display());
346 return;
347 }
348
349 let engine = create_engine();
350 let context = ScriptRunner::ScriptContext {
351 profile_name:"bundler-preparation".to_string(),
352 cwd:".".to_string(),
353 manifest_dir:".".to_string(),
354 target_triple:None,
355 workbench_type:None,
356 features:HashMap::new(),
357 };
358
359 let result = ScriptRunner::ExecuteProfileScript(&engine, full_script_path.to_str().unwrap(), &context);
360
361 assert!(
362 result.is_ok(),
363 "Script execution should succeed but got error: {:?}",
364 result.err()
365 );
366 let script_result = result.unwrap();
367 assert!(script_result.success, "Script execution should report success");
368 assert!(!script_result.env_vars.is_empty(), "Script should return environment variables");
369
370 assert_eq!(script_result.env_vars.get("BUNDLER_TYPE"), Some(&"swc".to_string()));
372 assert_eq!(script_result.env_vars.get("SWC_TARGET"), Some(&"esnext".to_string()));
373 }
374
375 #[test]
376 fn test_execute_profile_script_swc_bundle() {
377 let config = ConfigLoader::load(".").expect("Failed to load configuration");
378 let profile =
379 ConfigLoader::get_profile(&config, "swc-bundle").expect("Profile 'swc-bundle' not found in configuration");
380
381 let script_path = profile
382 .rhai_script
383 .as_ref()
384 .expect("No Rhai script defined for swc-bundle profile");
385 let full_script_path = std::path::Path::new(".").join(".vscode").join(script_path);
386
387 if !full_script_path.exists() {
388 eprintln!("Skipping test - script file not found: {}", full_script_path.display());
389 return;
390 }
391
392 let engine = create_engine();
393 let context = ScriptRunner::ScriptContext {
394 profile_name:"swc-bundle".to_string(),
395 cwd:".".to_string(),
396 manifest_dir:".".to_string(),
397 target_triple:None,
398 workbench_type:None,
399 features:HashMap::new(),
400 };
401
402 let result = ScriptRunner::ExecuteProfileScript(&engine, full_script_path.to_str().unwrap(), &context);
403
404 assert!(
405 result.is_ok(),
406 "Script execution should succeed but got error: {:?}",
407 result.err()
408 );
409 let script_result = result.unwrap();
410 assert!(script_result.success, "Script execution should report success");
411 assert!(!script_result.env_vars.is_empty(), "Script should return environment variables");
412
413 assert_eq!(script_result.env_vars.get("BUNDLER_TYPE"), Some(&"swc".to_string()));
414 assert_eq!(script_result.env_vars.get("NODE_ENV"), Some(&"production".to_string()));
415 }
416
417 #[test]
418 fn test_execute_profile_script_oxc_bundle() {
419 let config = ConfigLoader::load(".").expect("Failed to load configuration");
420 let profile =
421 ConfigLoader::get_profile(&config, "oxc-bundle").expect("Profile 'oxc-bundle' not found in configuration");
422
423 let script_path = profile
424 .rhai_script
425 .as_ref()
426 .expect("No Rhai script defined for oxc-bundle profile");
427 let full_script_path = std::path::Path::new(".").join(".vscode").join(script_path);
428
429 if !full_script_path.exists() {
430 eprintln!("Skipping test - script file not found: {}", full_script_path.display());
431 return;
432 }
433
434 let engine = create_engine();
435 let context = ScriptRunner::ScriptContext {
436 profile_name:"oxc-bundle".to_string(),
437 cwd:".".to_string(),
438 manifest_dir:".".to_string(),
439 target_triple:None,
440 workbench_type:None,
441 features:HashMap::new(),
442 };
443
444 let result = ScriptRunner::ExecuteProfileScript(&engine, full_script_path.to_str().unwrap(), &context);
445
446 assert!(
447 result.is_ok(),
448 "Script execution should succeed but got error: {:?}",
449 result.err()
450 );
451 let script_result = result.unwrap();
452 assert!(script_result.success, "Script execution should report success");
453 assert!(!script_result.env_vars.is_empty(), "Script should return environment variables");
454
455 assert_eq!(script_result.env_vars.get("BUNDLER_TYPE"), Some(&"oxc".to_string()));
456 assert_eq!(script_result.env_vars.get("NODE_ENV"), Some(&"production".to_string()));
457 }
458
459 #[test]
460 fn test_env_vars_match_static_config() {
461 let config = ConfigLoader::load(".").expect("Failed to load configuration");
462
463 for profile_name in &["debug", "production", "release"] {
464 let profile = ConfigLoader::get_profile(&config, profile_name)
465 .expect(&format!("Profile '{}' not found", profile_name));
466
467 let script_path = profile
468 .rhai_script
469 .as_ref()
470 .expect(&format!("No Rhai script defined for {}", profile_name));
471
472 let full_script_path = std::path::Path::new(".").join(".vscode").join(script_path);
473
474 if !full_script_path.exists() {
475 continue;
476 }
477
478 let engine = create_engine();
479 let context = ScriptRunner::ScriptContext {
480 profile_name:profile_name.to_string(),
481 cwd:".".to_string(),
482 manifest_dir:".".to_string(),
483 target_triple:None,
484 workbench_type:None,
485 features:HashMap::new(),
486 };
487
488 let script_result =
489 ScriptRunner::ExecuteProfileScript(&engine, full_script_path.to_str().unwrap(), &context)
490 .expect(&format!("Failed to execute script for profile '{}'", profile_name));
491
492 let static_env = ConfigLoader::resolve_profile_env(&config, profile_name);
493
494 if let Some(static_debug) = static_env.get("Debug") {
497 let dynamic_debug = script_result.env_vars.get("Debug");
498 assert_eq!(
499 dynamic_debug,
500 Some(static_debug),
501 "Debug value should match between static config and Rhai script for profile '{}'",
502 profile_name
503 );
504 }
505 }
506 }
507}