use std::{path::Path, process::Command};
use anyhow::{Context, Ok, Result};
use clap::ValueEnum;
use std::fs::File;
use crate::nix::run_nix_command;
use flk::flake::parsers::{flake::parse_flake, utils::resolve_profile};
use flk::utils::visual::with_spinner;
#[derive(Debug, Clone, ValueEnum)]
#[value(rename_all = "lowercase")]
pub enum ExportType {
Docker,
Podman,
Json,
}
pub fn run_export(export_type: &ExportType, target_profile: Option<String>) -> Result<()> {
let profile = resolve_profile(target_profile)?;
match export_type {
ExportType::Docker => {
println!("Exporting flake.nix to Docker image...");
let (_, _, success) = with_spinner("<export-docker>", || {
run_nix_command(&[
"build",
&format!(".#docker-{}", profile.as_str()),
"--out-link",
".flk/result",
"--impure",
])
.context("Failed to build Docker image from flake.nix")
})?;
println!("Docker image created successfully ✅");
let file = File::open(".flk/result").context("Failed to open .flk/result")?;
let output = with_spinner("<load-image>", || {
Command::new("docker")
.args(["load"])
.stdin(file)
.output()
.context("Failed to load Docker image")
})?;
println!(
"Docker image export {}",
if success {
"succeeded ✅"
} else {
"failed ❌"
}
);
println!("{}", String::from_utf8_lossy(&output.stdout));
}
ExportType::Podman => {
println!("Exporting flake.nix to Podman image...");
let (_, _, success) = with_spinner("<export-podman>", || {
run_nix_command(&[
"build",
&format!(".#podman-{}", profile.as_str()),
"--out-link",
".flk/result",
"--impure",
])
.context("Failed to build Podman image from flake.nix")
})?;
println!("Podman image created successfully ✅");
let file = File::open(".flk/result").context("Failed to open .flk/result")?;
let output = with_spinner("<load-image>", || {
Command::new("podman")
.args(["load"])
.stdin(file)
.output()
.context("Failed to load Podman image")
})?;
println!(
"Podman image export {}",
if success {
"succeeded ✅"
} else {
"failed ❌"
}
);
println!("{}", String::from_utf8_lossy(&output.stdout));
}
ExportType::Json => {
let flake_path = Path::new("flake.nix");
let flake_content = parse_flake(flake_path.to_str().unwrap())?;
let json_output = serde_json::to_string_pretty(&flake_content)
.context("Failed to serialize flake content to JSON")?;
std::fs::write("flake.json", json_output).context("Failed to write flake.json file")?;
println!("Flake export to JSON succeeded ✅");
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::commands::cwd_test_guard;
use tempfile::TempDir;
fn setup_flake(dir: &TempDir) {
std::fs::write(
dir.path().join("flake.nix"),
r#"{
description = "test";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
}
"#,
)
.unwrap();
let flk = dir.path().join(".flk");
std::fs::create_dir_all(flk.join("profiles")).unwrap();
std::fs::write(flk.join("default.nix"), "{ defaultShell = \"rust\"; }\n").unwrap();
std::fs::write(
flk.join("profiles").join("rust.nix"),
r#"{pkgs, ...}: {
packages = [
pkgs.ripgrep
];
envVars = {};
commands = [
];
shellHook = '''';
}
"#,
)
.unwrap();
std::env::set_current_dir(dir.path()).unwrap();
}
#[test]
fn json_export_writes_flake_json_in_cwd() {
let _guard = cwd_test_guard();
let tmp = TempDir::new().unwrap();
setup_flake(&tmp);
run_export(&ExportType::Json, Some("rust".into()))
.unwrap_or_else(|e| panic!("export failed: {e:?}"));
let json = std::fs::read_to_string(tmp.path().join("flake.json")).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert!(parsed.is_object(), "expected JSON object, got {parsed}");
}
#[test]
fn json_export_fails_when_flake_missing() {
let _guard = cwd_test_guard();
let tmp = TempDir::new().unwrap();
let flk = tmp.path().join(".flk");
std::fs::create_dir_all(flk.join("profiles")).unwrap();
std::fs::write(flk.join("default.nix"), "{ defaultShell = \"rust\"; }\n").unwrap();
std::fs::write(
flk.join("profiles").join("rust.nix"),
r#"{pkgs, ...}: {
packages = [
pkgs.ripgrep
];
envVars = {};
commands = [
];
shellHook = '''';
}
"#,
)
.unwrap();
std::env::set_current_dir(tmp.path()).unwrap();
let err = run_export(&ExportType::Json, Some("rust".into())).unwrap_err();
let msg = format!("{err:?}");
assert!(
msg.contains("flake.nix") || msg.to_lowercase().contains("no such file"),
"unexpected error: {msg}"
);
}
}