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}