helix/dna/mds/
export.rs

1use std::path::PathBuf;
2use anyhow::{Result, Context};
3use crate::mds::init::ProjectManifest;
4use serde::{Serialize, Deserialize};
5use serde_json;
6use serde_yaml;
7use toml;
8use chrono;
9
10
11pub fn export_project(
12    format: String,
13    output: Option<PathBuf>,
14    include_deps: bool,
15    verbose: bool,
16) -> Result<()> {
17    let project_dir = find_project_root()?;
18    if verbose {
19        println!("📤 Exporting HELIX project:");
20        println!("  Project: {}", project_dir.display());
21        println!("  Format: {}", format);
22        println!("  Include dependencies: {}", include_deps);
23    }
24    match format.as_str() {
25        "json" => export_to_json(&project_dir, output, include_deps, verbose)?,
26        "yaml" => export_to_yaml(&project_dir, output, include_deps, verbose)?,
27        "toml" => export_to_toml(&project_dir, output, include_deps, verbose)?,
28        "docker" => export_to_docker(&project_dir, output, verbose)?,
29        "k8s" => export_to_kubernetes(&project_dir, output, verbose)?,
30        _ => {
31            return Err(
32                anyhow::anyhow!(
33                    "Unknown export format '{}'. Available formats: json, yaml, toml, docker, k8s",
34                    format
35                ),
36            );
37        }
38    }
39    println!("✅ Export completed successfully!");
40    Ok(())
41}
42pub fn import_project(
43    input: PathBuf,
44    format: Option<String>,
45    force: bool,
46    verbose: bool,
47) -> Result<()> {
48    if verbose {
49        println!("📥 Importing HELIX project:");
50        println!("  Input: {}", input.display());
51        println!("  Format: {}", format.as_deref().unwrap_or("auto-detect"));
52    }
53    if !input.exists() {
54        return Err(anyhow::anyhow!("Input file not found: {}", input.display()));
55    }
56    let detected_format = format
57        .unwrap_or_else(|| {
58            if let Some(extension) = input.extension().and_then(|ext| ext.to_str()) {
59                match extension {
60                    "json" => "json".to_string(),
61                    "yaml" | "yml" => "yaml".to_string(),
62                    "toml" => "toml".to_string(),
63                    _ => "json".to_string(),
64                }
65            } else {
66                "json".to_string()
67            }
68        });
69    match detected_format.as_str() {
70        "json" => import_from_json(&input, force, verbose)?,
71        "yaml" => import_from_yaml(&input, force, verbose)?,
72        "toml" => import_from_toml(&input, force, verbose)?,
73        _ => {
74            return Err(
75                anyhow::anyhow!("Unsupported import format: {}", detected_format),
76            );
77        }
78    }
79    println!("✅ Import completed successfully!");
80    Ok(())
81}
82pub fn export_to_json(
83    project_dir: &PathBuf,
84    output: Option<PathBuf>,
85    include_deps: bool,
86    verbose: bool,
87) -> Result<()> {
88    let manifest = read_project_manifest(project_dir)?;
89    let source_files = collect_source_files(project_dir)?;
90    let export_data = ExportData {
91        manifest,
92        source_files,
93        dependencies: if include_deps {
94            Some(collect_dependencies(project_dir)?)
95        } else {
96            None
97        },
98        metadata: ExportMetadata {
99            exported_at: chrono::Utc::now().to_rfc3339(),
100            format_version: "1.0".to_string(),
101            tool_version: env!("CARGO_PKG_VERSION").to_string(),
102        },
103    };
104    let json_content = serde_json::to_string_pretty(&export_data)
105        .context("Failed to serialize to JSON")?;
106    let output_path = output.unwrap_or_else(|| project_dir.join("export.json"));
107    std::fs::write(&output_path, json_content).context("Failed to write JSON export")?;
108    if verbose {
109        println!("  ✅ Exported to: {}", output_path.display());
110    }
111    Ok(())
112}
113pub fn export_to_yaml(
114    project_dir: &PathBuf,
115    output: Option<PathBuf>,
116    include_deps: bool,
117    verbose: bool,
118) -> Result<()> {
119    let manifest = read_project_manifest(project_dir)?;
120    let source_files = collect_source_files(project_dir)?;
121    let export_data = ExportData {
122        manifest,
123        source_files,
124        dependencies: if include_deps {
125            Some(collect_dependencies(project_dir)?)
126        } else {
127            None
128        },
129        metadata: ExportMetadata {
130            exported_at: chrono::Utc::now().to_rfc3339(),
131            format_version: "1.0".to_string(),
132            tool_version: env!("CARGO_PKG_VERSION").to_string(),
133        },
134    };
135    let yaml_content = serde_yaml::to_string(&export_data)
136        .context("Failed to serialize to YAML")?;
137    let output_path = output.unwrap_or_else(|| project_dir.join("export.yaml"));
138    std::fs::write(&output_path, yaml_content).context("Failed to write YAML export")?;
139    if verbose {
140        println!("  ✅ Exported to: {}", output_path.display());
141    }
142    Ok(())
143}
144pub fn export_to_toml(
145    project_dir: &PathBuf,
146    output: Option<PathBuf>,
147    include_deps: bool,
148    verbose: bool,
149) -> Result<()> {
150    let manifest = read_project_manifest(project_dir)?;
151    let toml_content = toml::to_string_pretty(&manifest)
152        .context("Failed to serialize to TOML")?;
153    let output_path = output.unwrap_or_else(|| project_dir.join("export.toml"));
154    std::fs::write(&output_path, toml_content).context("Failed to write TOML export")?;
155    if verbose {
156        println!("  ✅ Exported to: {}", output_path.display());
157        if include_deps {
158            println!("  Note: Dependencies not yet supported in TOML export");
159        }
160    }
161    Ok(())
162}
163pub fn export_to_docker(
164    project_dir: &PathBuf,
165    output: Option<PathBuf>,
166    verbose: bool,
167) -> Result<()> {
168    let manifest = read_project_manifest(project_dir)?;
169    let dockerfile_content = format!(
170        r#"# Dockerfile for HELIX project: {}
171FROM ubuntu:22.04
172
173# Install hlx runtime
174RUN apt-get update && apt-get install -y \
175    curl \
176    && rm -rf /var/lib/apt/lists/*
177
178# Install hlx compiler
179RUN curl -sSL https://get.helix.cm/install.sh | bash
180
181# Set working directory
182WORKDIR /app
183
184# Copy project files
185COPY . .
186
187# Build the project
188RUN helix build
189
190# Expose port
191EXPOSE 8080
192
193# Run the project
194CMD ["helix", "run"]
195"#,
196        manifest.name
197    );
198    let output_path = output.unwrap_or_else(|| project_dir.join("Dockerfile"));
199    std::fs::write(&output_path, dockerfile_content)
200        .context("Failed to write Dockerfile")?;
201    if verbose {
202        println!("  ✅ Exported to: {}", output_path.display());
203    }
204    Ok(())
205}
206pub fn export_to_kubernetes(
207    project_dir: &PathBuf,
208    output: Option<PathBuf>,
209    verbose: bool,
210) -> Result<()> {
211    let manifest = read_project_manifest(project_dir)?;
212    let k8s_content = format!(
213        r#"apiVersion: apps/v1
214kind: Deployment
215metadata:
216  name: {}-deployment
217  labels:
218    app: {}
219spec:
220  replicas: 3
221  selector:
222    matchLabels:
223      app: {}
224  template:
225    metadata:
226      labels:
227        app: {}
228    spec:
229      containers:
230      - name: {}
231        image: {}:latest
232        ports:
233        - containerPort: 8080
234        env:
235        - name: hlx_ENV
236          value: "production"
237---
238apiVersion: v1
239kind: Service
240metadata:
241  name: {}-service
242spec:
243  selector:
244    app: {}
245  ports:
246  - protocol: TCP
247    port: 80
248    targetPort: 8080
249  type: LoadBalancer
250"#,
251        manifest.name, manifest.name, manifest.name, manifest
252        .name, manifest.name, manifest.name, manifest
253        .name, manifest.name
254    );
255    let output_path = output.unwrap_or_else(|| project_dir.join("k8s.yaml"));
256    std::fs::write(&output_path, k8s_content)
257        .context("Failed to write Kubernetes manifest")?;
258    if verbose {
259        println!("  ✅ Exported to: {}", output_path.display());
260    }
261    Ok(())
262}
263pub fn import_from_json(input: &PathBuf, force: bool, verbose: bool) -> Result<()> {
264    let content = std::fs::read_to_string(input).context("Failed to read input file")?;
265    let export_data: ExportData = serde_json::from_str(&content)
266        .context("Failed to parse JSON")?;
267    import_export_data(export_data, force, verbose)
268}
269pub fn import_from_yaml(input: &PathBuf, force: bool, verbose: bool) -> Result<()> {
270    let content = std::fs::read_to_string(input).context("Failed to read input file")?;
271    let export_data: ExportData = serde_yaml::from_str(&content)
272        .context("Failed to parse YAML")?;
273    import_export_data(export_data, force, verbose)
274}
275pub fn import_from_toml(input: &PathBuf, force: bool, verbose: bool) -> Result<()> {
276    let content = std::fs::read_to_string(input).context("Failed to read input file")?;
277    let manifest: ProjectManifest = toml::from_str(&content)
278        .context("Failed to parse TOML")?;
279    let export_data = ExportData {
280        manifest,
281        source_files: std::collections::HashMap::new(),
282        dependencies: None,
283        metadata: ExportMetadata {
284            exported_at: chrono::Utc::now().to_rfc3339(),
285            format_version: "1.0".to_string(),
286            tool_version: env!("CARGO_PKG_VERSION").to_string(),
287        },
288    };
289    import_export_data(export_data, force, verbose)
290}
291pub fn import_export_data(
292    export_data: ExportData,
293    force: bool,
294    verbose: bool,
295) -> Result<()> {
296    let project_dir = std::env::current_dir()
297        .context("Failed to get current directory")?;
298    let manifest_path = project_dir.join("project.hlx");
299    if manifest_path.exists() && !force {
300        return Err(anyhow::anyhow!("Project already exists. Use --force to overwrite."));
301    }
302    std::fs::create_dir_all(project_dir.join("src"))
303        .context("Failed to create src directory")?;
304    let manifest_content = toml::to_string_pretty(&export_data.manifest)
305        .context("Failed to serialize manifest")?;
306    std::fs::write(&manifest_path, manifest_content)
307        .context("Failed to write manifest")?;
308    let source_files_count = export_data.source_files.len();
309    for (filename, content) in export_data.source_files {
310        let file_path = project_dir.join("src").join(filename);
311        std::fs::write(&file_path, content).context("Failed to write source file")?;
312    }
313    if verbose {
314        println!("  ✅ Imported {} source files", source_files_count);
315    }
316    Ok(())
317}
318#[derive(Debug, Serialize, Deserialize)]
319struct ExportData {
320    manifest: ProjectManifest,
321    source_files: std::collections::HashMap<String, String>,
322    dependencies: Option<std::collections::HashMap<String, String>>,
323    metadata: ExportMetadata,
324}
325#[derive(Debug, Serialize, Deserialize)]
326struct ExportMetadata {
327    exported_at: String,
328    format_version: String,
329    tool_version: String,
330}
331pub fn read_project_manifest(
332    project_dir: &PathBuf,
333) -> Result<ProjectManifest> {
334    let manifest_path = project_dir.join("project.hlx");
335    if !manifest_path.exists() {
336        return Err(
337            anyhow::anyhow!(
338                "No project.hlx found. Run 'helix init' first to create a project."
339            ),
340        );
341    }
342    let content = std::fs::read_to_string(&manifest_path)
343        .context("Failed to read project.hlx")?;
344    let project_name = extract_project_name(&content)?;
345    let version = extract_project_version(&content)?;
346    let manifest = ProjectManifest {
347        name: project_name,
348        version,
349        description: Some("HELIX project".to_string()),
350        author: Some("HELIX Developer".to_string()),
351        license: Some("MIT".to_string()),
352        repository: None,
353        created: Some(chrono::Utc::now().format("%Y-%m-%d").to_string()),
354    };
355    Ok(manifest)
356}
357pub fn collect_source_files(
358    project_dir: &PathBuf,
359) -> Result<std::collections::HashMap<String, String>> {
360    let mut files = std::collections::HashMap::new();
361    let src_dir = project_dir.join("src");
362    if src_dir.exists() {
363        collect_helix_files(&src_dir, &mut files)?;
364    }
365    Ok(files)
366}
367pub fn collect_helix_files(
368    dir: &PathBuf,
369    files: &mut std::collections::HashMap<String, String>,
370) -> Result<()> {
371    let entries = std::fs::read_dir(dir).context("Failed to read directory")?;
372    for entry in entries {
373        let entry = entry.context("Failed to read directory entry")?;
374        let path = entry.path();
375        if path.is_file() {
376            if let Some(extension) = path.extension() {
377                if extension == "hlx" {
378                    let filename = path
379                        .file_name()
380                        .and_then(|n| n.to_str())
381                        .ok_or_else(|| anyhow::anyhow!("Invalid filename"))?;
382                    let content = std::fs::read_to_string(&path)
383                        .context("Failed to read file")?;
384                    files.insert(filename.to_string(), content);
385                }
386            }
387        } else if path.is_dir() {
388            collect_helix_files(&path, files)?;
389        }
390    }
391    Ok(())
392}
393pub fn collect_dependencies(
394    _project_dir: &PathBuf,
395) -> Result<std::collections::HashMap<String, String>> {
396    Ok(std::collections::HashMap::new())
397}
398pub fn extract_project_name(content: &str) -> Result<String> {
399    for line in content.lines() {
400        let trimmed = line.trim();
401        if trimmed.starts_with("project \"") {
402            if let Some(start) = trimmed.find('"') {
403                if let Some(end) = trimmed[start + 1..].find('"') {
404                    return Ok(trimmed[start + 1..start + 1 + end].to_string());
405                }
406            }
407        }
408    }
409    Err(anyhow::anyhow!("Could not find project name in HELIX file"))
410}
411pub fn extract_project_version(content: &str) -> Result<String> {
412    for line in content.lines() {
413        let trimmed = line.trim();
414        if trimmed.starts_with("version = \"") {
415            if let Some(start) = trimmed.find('"') {
416                if let Some(end) = trimmed[start + 1..].find('"') {
417                    return Ok(trimmed[start + 1..start + 1 + end].to_string());
418                }
419            }
420        }
421    }
422    Ok("0.1.0".to_string())
423}
424pub fn find_project_root() -> Result<PathBuf> {
425    let mut current_dir = std::env::current_dir()
426        .context("Failed to get current directory")?;
427    loop {
428        let manifest_path = current_dir.join("project.hlx");
429        if manifest_path.exists() {
430            return Ok(current_dir);
431        }
432        if let Some(parent) = current_dir.parent() {
433            current_dir = parent.to_path_buf();
434        } else {
435            break;
436        }
437    }
438    Err(anyhow::anyhow!("No HELIX project found. Run 'helix init' first."))
439}