mod formats;
mod pdf;
mod presets;
use std::path::{Path, PathBuf};
use miette::Diagnostic;
use thiserror::Error;
use crate::commands::check::{self, CheckError};
use crate::commands::target::resolve_entry;
use crate::tscircuit::{Runner, RunnerError};
#[derive(Debug, Error, Diagnostic)]
pub enum ExportError {
#[error(transparent)]
#[diagnostic(transparent)]
Check(#[from] CheckError),
#[error(transparent)]
#[diagnostic(transparent)]
Runner(#[from] RunnerError),
#[error("failed to export manufacturing files: {0}")]
#[diagnostic(code(fusible::export::io_error))]
Io(#[from] std::io::Error),
#[error("unknown export preset \"{preset}\"")]
#[diagnostic(
code(fusible::export::unknown_preset),
help("valid presets are: `fab` (gerbers + json), `cad` (kicad + step), `docs` (svg + pdf), or `all`")
)]
UnknownPreset { preset: String },
}
struct ExportJob {
runner: Option<Runner>,
entry: PathBuf,
board_title: String,
output: Option<PathBuf>,
source: Option<String>,
}
impl ExportJob {
fn runner(&self) -> Result<&Runner, RunnerError> {
self.runner.as_ref().ok_or(RunnerError::NotFound)
}
fn output_or(&self, default: &str) -> PathBuf {
self.output
.clone()
.unwrap_or_else(|| PathBuf::from(default))
}
fn circuit_json(&self) -> Result<String, ExportError> {
match &self.source {
Some(source) => Ok(source.clone()),
None => Ok(check::load_circuit_source(&self.entry)?.1),
}
}
}
pub fn run(
file: Option<PathBuf>,
preset: Option<&str>,
format: &str,
output: Option<PathBuf>,
skip_lint: bool,
) -> Result<(), ExportError> {
if let Some(preset_name) = preset {
presets::validate_preset(preset_name)?;
}
let (entry, source) = fuse_gate(file, skip_lint)?;
let job = ExportJob {
runner: Runner::detect().ok(),
entry,
board_title: check::resolve_board_name(),
output,
source,
};
match preset {
Some(preset_name) => presets::run_preset(&job, preset_name),
None => formats::run_format(&job, format),
}
}
fn fuse_gate(
file: Option<PathBuf>,
skip_lint: bool,
) -> Result<(PathBuf, Option<String>), ExportError> {
if skip_lint {
println!("⚠️ Skipping electrical rule verification (--skip-lint requested).\n");
let entry = resolve_entry(file).unwrap_or_else(|_| PathBuf::from("index.tsx"));
return Ok((entry, None));
}
println!("🔍 Verifying electrical rules before export (fuse check)...");
let entry = resolve_entry(file).map_err(CheckError::from)?;
let (_, source) = check::lint_source(&entry, None)?;
println!("⚡ Fuse intact — proceeding with artifact export.\n");
Ok((entry, Some(source)))
}
fn extract_zip_archive(zip_path: &Path, target_dir: &Path) -> Result<(), ExportError> {
let file = std::fs::File::open(zip_path)?;
zip::ZipArchive::new(file)
.and_then(|mut archive| archive.extract(target_dir))
.map_err(|err| ExportError::Io(std::io::Error::other(err)))
}