greentic_dev/
pack_run.rs

1use std::fs;
2use std::path::{Path, PathBuf};
3
4use anyhow::{Context, Result};
5use greentic_runner::desktop::{
6    HttpMock, HttpMockMode, MocksConfig, OtlpHook, Runner, SigningPolicy, ToolsMock,
7};
8use serde_json::{Value as JsonValue, json};
9
10#[derive(Debug, Clone)]
11pub struct PackRunConfig<'a> {
12    pub pack_path: &'a Path,
13    pub entry: Option<String>,
14    pub input: Option<String>,
15    pub policy: RunPolicy,
16    pub otlp: Option<String>,
17    pub allow_hosts: Option<Vec<String>>,
18    pub mocks: MockSetting,
19    pub artifacts_dir: Option<&'a Path>,
20}
21
22#[derive(Debug, Clone, Copy)]
23pub enum RunPolicy {
24    Strict,
25    DevOk,
26}
27
28#[derive(Debug, Clone, Copy)]
29pub enum MockSetting {
30    On,
31    Off,
32}
33
34pub fn run(config: PackRunConfig<'_>) -> Result<()> {
35    let input_value = parse_input(config.input)?;
36    let otlp_hook = config.otlp.map(|endpoint| OtlpHook {
37        endpoint,
38        headers: Vec::new(),
39        sample_all: true,
40    });
41    let allow_hosts = config.allow_hosts.unwrap_or_default();
42    let mocks_config = build_mocks_config(config.mocks, allow_hosts)?;
43
44    let artifacts_override = config.artifacts_dir.map(|dir| dir.to_path_buf());
45    if let Some(dir) = &artifacts_override {
46        fs::create_dir_all(dir)
47            .with_context(|| format!("failed to create artifacts directory {}", dir.display()))?;
48    }
49
50    let runner = Runner::new();
51    let run_result = runner
52        .run_pack_with(config.pack_path, |opts| {
53            opts.entry_flow = config.entry.clone();
54            opts.input = input_value.clone();
55            opts.signing = signing_policy(config.policy);
56            if let Some(hook) = otlp_hook.clone() {
57                opts.otlp = Some(hook);
58            }
59            opts.mocks = mocks_config.clone();
60            opts.artifacts_dir = artifacts_override.clone();
61        })
62        .context("pack execution failed")?;
63
64    let rendered =
65        serde_json::to_string_pretty(&run_result).context("failed to render run result JSON")?;
66    println!("{rendered}");
67
68    Ok(())
69}
70
71fn parse_input(input: Option<String>) -> Result<JsonValue> {
72    if let Some(raw) = input {
73        if raw.trim().is_empty() {
74            return Ok(json!({}));
75        }
76        serde_json::from_str(&raw).context("failed to parse --input JSON")
77    } else {
78        Ok(json!({}))
79    }
80}
81
82fn build_mocks_config(setting: MockSetting, allow_hosts: Vec<String>) -> Result<MocksConfig> {
83    let mut config = MocksConfig {
84        net_allowlist: allow_hosts
85            .into_iter()
86            .map(|host| host.trim().to_ascii_lowercase())
87            .filter(|host| !host.is_empty())
88            .collect(),
89        ..MocksConfig::default()
90    };
91
92    if matches!(setting, MockSetting::On) {
93        config.http = Some(HttpMock {
94            record_replay_dir: None,
95            mode: HttpMockMode::RecordReplay,
96            rewrites: Vec::new(),
97        });
98
99        let tools_dir = PathBuf::from(".greentic").join("mocks").join("tools");
100        fs::create_dir_all(&tools_dir)
101            .with_context(|| format!("failed to create {}", tools_dir.display()))?;
102        config.mcp_tools = Some(ToolsMock {
103            directory: None,
104            script_dir: Some(tools_dir),
105            short_circuit: true,
106        });
107    }
108
109    Ok(config)
110}
111
112fn signing_policy(policy: RunPolicy) -> SigningPolicy {
113    match policy {
114        RunPolicy::Strict => SigningPolicy::Strict,
115        RunPolicy::DevOk => SigningPolicy::DevOk,
116    }
117}