use std::path::Path;
use anyhow::{Context, Result, bail};
use super::super::config::BuildConfig;
use super::super::route::CacheStats;
use super::super::types::ViteDevInfo;
use crate::config::SeamConfig;
use crate::ui;
#[derive(Debug, Clone, Copy)]
pub enum RebuildMode {
Full,
FrontendOnly,
}
pub(super) fn maybe_generate_rpc_hashes(
build_config: &BuildConfig,
manifest: &seam_codegen::Manifest,
out_dir: &Path,
) -> Result<Option<seam_codegen::RpcHashMap>> {
if !build_config.obfuscate {
return Ok(None);
}
let names: Vec<&str> = manifest.procedures.keys().map(std::string::String::as_str).collect();
let salt = build_config
.rpc_salt
.as_deref()
.map(std::string::ToString::to_string)
.unwrap_or_else(seam_codegen::generate_random_salt);
let map = seam_codegen::generate_rpc_hash_map(
&names,
&salt,
build_config.hash_length,
build_config.type_hint,
)?;
let path = out_dir.join("rpc-hash-map.json");
std::fs::write(&path, serde_json::to_string_pretty(&map)?)?;
ui::detail_ok(&format!("{}rpc-hash-map.json{}", ui::col(ui::DIM), ui::col(ui::RESET)));
Ok(Some(map))
}
pub(super) fn dispatch_extract_manifest(
build_config: &BuildConfig,
base_dir: &Path,
out_dir: &Path,
) -> Result<seam_codegen::Manifest> {
use super::super::route::{extract_manifest, extract_manifest_command};
use anyhow::Context;
if let Some(cmd) = &build_config.manifest_command {
extract_manifest_command(base_dir, cmd, out_dir)
} else {
let router_file = build_config
.router_file
.as_deref()
.context("either router_file or manifest_command is required")?;
extract_manifest(base_dir, router_file, out_dir)
}
}
pub fn maybe_generate_rpc_hashes_pub(
build_config: &BuildConfig,
manifest: &seam_codegen::Manifest,
out_dir: &std::path::Path,
) -> Result<Option<seam_codegen::RpcHashMap>> {
maybe_generate_rpc_hashes(build_config, manifest, out_dir)
}
pub(super) fn vite_info_from_config(config: &SeamConfig, proxied: bool) -> Option<ViteDevInfo> {
config.dev.vite_port.map(|port| ViteDevInfo {
origin: if proxied { String::new() } else { format!("http://localhost:{port}") },
entry: config
.frontend
.entry
.clone()
.expect("frontend.entry is required when dev.vite_port is set"),
})
}
pub(super) fn print_cache_stats(cache: &Option<CacheStats>) {
if let Some(stats) = cache {
ui::detail_ok(&format!(
"{}skeleton cache: {} hit, {} miss{}",
ui::col(ui::DIM),
stats.hits,
stats.misses,
ui::col(ui::RESET)
));
}
}
pub(super) fn run_fs_router(base_dir: &Path, pages_dir: &str, output_path: &Path) -> Result<()> {
let script = crate::shell::resolve_node_module(base_dir, "@canmi/seam-router/dist/cli.js")
.ok_or_else(|| anyhow::anyhow!("@canmi/seam-router not found -- install it"))?;
let runtime = if which_exists("bun") { "bun" } else { "node" };
let pages_abs = base_dir.join(pages_dir);
let output_abs =
if output_path.is_absolute() { output_path.to_path_buf() } else { base_dir.join(output_path) };
let status = std::process::Command::new(runtime)
.arg(script.to_string_lossy().as_ref())
.arg(pages_abs.to_string_lossy().as_ref())
.arg(output_abs.to_string_lossy().as_ref())
.current_dir(base_dir)
.status()
.with_context(|| format!("failed to run {runtime} for fs router generation"))?;
if !status.success() {
bail!("fs router generation failed (exit code: {})", status.code().unwrap_or(-1));
}
Ok(())
}
fn which_exists(name: &str) -> bool {
std::process::Command::new("which")
.arg(name)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok_and(|s| s.success())
}