use std::path::Path;
use anyhow::{Context, Result};
use super::super::config::BuildConfig;
use super::super::route::generate_types;
use super::super::route::{
BundleContext, RenderContext, build_reference_graph, export_i18n, generate_derive_registry_ts,
generate_route_procedures_ts, inject_route_procedures, inject_route_projections,
package_static_assets, process_routes, read_i18n_messages, report_narrowing_savings,
validate_derive_sources, validate_handoff_consistency, validate_invalidates,
validate_procedure_references, warn_unused_queries,
};
use super::super::types::AssetFiles;
use super::helpers;
use super::helpers::{
RebuildMode, dispatch_extract_manifest, maybe_generate_rpc_hashes, vite_info_from_config,
};
use super::steps;
use crate::config::SeamConfig;
pub fn run_incremental_rebuild(
config: &SeamConfig,
build_config: &BuildConfig,
base_dir: &Path,
mode: RebuildMode,
) -> Result<()> {
let out_dir = base_dir.join(&build_config.out_dir);
let vite = vite_info_from_config(config, true);
let is_vite = vite.is_some();
if let Some(pages_dir) = &build_config.pages_dir {
let output = base_dir.join(".seam/generated/routes.ts");
helpers::run_fs_router(base_dir, pages_dir, &output)?;
}
if matches!(mode, RebuildMode::Full) {
let manifest = dispatch_extract_manifest(build_config, base_dir, &out_dir)?;
let rpc_hashes = maybe_generate_rpc_hashes(build_config, &manifest, &out_dir)?;
generate_types(&manifest, config, rpc_hashes.as_ref(), base_dir)?;
copy_wasm_binary(base_dir, &out_dir)?;
}
let rpc_map_path = out_dir.join("rpc-hash-map.json");
let rpc_map_path_str =
if rpc_map_path.exists() { rpc_map_path.to_string_lossy().to_string() } else { String::new() };
let bundler_env = steps::build_bundler_env(build_config, &rpc_map_path_str);
let assets = if is_vite {
AssetFiles { css: vec![], js: vec![] }
} else {
steps::bundle_frontend(build_config, base_dir, &bundler_env)?
};
let skeleton_output =
steps::render_skeletons(build_config, base_dir, &out_dir.join("seam-manifest.json"))?;
let manifest_json_path = out_dir.join("seam-manifest.json");
let manifest_str = std::fs::read_to_string(&manifest_json_path)
.with_context(|| format!("failed to read {}", manifest_json_path.display()))?;
let manifest: seam_codegen::Manifest = serde_json::from_str(&manifest_str)
.with_context(|| format!("failed to parse {}", manifest_json_path.display()))?;
let ref_graph = build_reference_graph(&manifest, &skeleton_output);
validate_procedure_references(&ref_graph)?;
validate_invalidates(&manifest)?;
validate_handoff_consistency(&ref_graph);
validate_derive_sources(&skeleton_output);
warn_unused_queries(&ref_graph, &manifest);
let rp_path = base_dir.join(".seam/generated/route-procedures.ts");
generate_route_procedures_ts(&ref_graph, &manifest, &rp_path)?;
let dr_path = base_dir.join(".seam/generated/derive-registry.ts");
generate_derive_registry_ts(&skeleton_output, &dr_path)?;
let templates_dir = out_dir.join("templates");
std::fs::create_dir_all(&templates_dir)
.with_context(|| format!("failed to create {}", templates_dir.display()))?;
let i18n_messages = match &build_config.i18n {
Some(cfg) => Some(read_i18n_messages(base_dir, cfg)?),
None => None,
};
let render = RenderContext {
root_id: &build_config.root_id,
data_id: &build_config.data_id,
dev_mode: true,
vite: vite.as_ref(),
};
let bundle_ctx = BundleContext { manifest: None, source_file_map: None };
let mut route_manifest = process_routes(
&skeleton_output.layouts,
&skeleton_output.routes,
&templates_dir,
&assets,
&render,
build_config.i18n.as_ref(),
&bundle_ctx,
)?;
inject_route_procedures(&mut route_manifest, &ref_graph);
inject_route_projections(&mut route_manifest, &out_dir)?;
report_narrowing_savings(&route_manifest);
if let (Some(msgs), Some(cfg)) = (&i18n_messages, &build_config.i18n) {
export_i18n(&out_dir, msgs, &mut route_manifest, cfg)?;
}
let meta = Some(super::super::route::build_manifest_meta(build_config));
steps::write_route_manifest(&out_dir, &mut route_manifest, meta)?;
if !is_vite {
package_static_assets(base_dir, &out_dir, build_config.dist_dir())?;
}
Ok(())
}
const WASM_BINARIES: &[(&str, &str, &str)] = &[
("injector.wasm", "@canmi/seam-injector/pkg", "src/server/injector/js/pkg"),
("engine.wasm", "@canmi/seam-engine/pkg", "src/server/engine/js/pkg"),
];
pub(super) fn copy_wasm_binary(base_dir: &Path, out_dir: &Path) -> Result<()> {
for &(filename, npm_path, workspace_path) in WASM_BINARIES {
let candidates: Vec<std::path::PathBuf> = [
Some(base_dir.join("node_modules").join(npm_path).join(filename)),
find_workspace_wasm(base_dir, workspace_path, filename),
]
.into_iter()
.flatten()
.collect();
for src in candidates {
if src.exists() {
let dest_dir = out_dir.join("pkg");
std::fs::create_dir_all(&dest_dir)
.with_context(|| format!("failed to create {}", dest_dir.display()))?;
std::fs::copy(&src, dest_dir.join(filename))
.with_context(|| format!("failed to copy WASM binary from {}", src.display()))?;
break;
}
}
}
Ok(())
}
pub fn copy_wasm_binary_pub(base_dir: &Path, out_dir: &Path) -> Result<()> {
copy_wasm_binary(base_dir, out_dir)
}
fn find_workspace_wasm(
base_dir: &Path,
workspace_path: &str,
filename: &str,
) -> Option<std::path::PathBuf> {
let mut dir = base_dir.to_path_buf();
for _ in 0..5 {
let candidate = dir.join(workspace_path).join(filename);
if candidate.exists() {
return Some(candidate);
}
if !dir.pop() {
break;
}
}
None
}