use aube_lockfile::LockfileGraph;
use aube_lockfile::dep_path_filename::dep_path_to_filename;
use aube_manifest::PackageJson;
use miette::{Context, IntoDiagnostic, miette};
use sha2::{Digest, Sha256};
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
#[allow(clippy::too_many_arguments)]
pub(crate) fn apply_injected(
root_dir: &Path,
modules_dir_name: &str,
aube_dir: &Path,
virtual_store_dir_max_length: usize,
graph: &LockfileGraph,
manifests: &[(String, PackageJson)],
ws_dirs: &BTreeMap<String, PathBuf>,
) -> miette::Result<usize> {
let name_to_importer: BTreeMap<String, String> = ws_dirs
.iter()
.filter_map(|(name, dir)| {
let rel = dir.strip_prefix(root_dir).ok()?;
Some((name.clone(), rel.to_string_lossy().into_owned()))
})
.collect();
let mut count = 0usize;
for (importer_path, manifest) in manifests {
let injected = manifest.dependencies_meta_injected();
if injected.is_empty() {
continue;
}
for dep_name in &injected {
let Some(src_dir) = ws_dirs.get(dep_name) else {
continue;
};
let src_manifest = PackageJson::from_path(&src_dir.join("package.json"))
.into_diagnostic()
.wrap_err_with(|| {
format!("inject: failed to read {}/package.json", src_dir.display())
})?;
let version = src_manifest.version.as_deref().unwrap_or("0.0.0");
let inject_key = format!("{importer_path}\0{dep_name}@{version}");
let consumer_hash = short_hash(&inject_key);
let inject_dep_path = format!("{dep_name}@{version}+inject_{consumer_hash}");
let entry_name = dep_path_to_filename(&inject_dep_path, virtual_store_dir_max_length);
let entry_root = aube_dir.join(&entry_name);
let entry_nm = entry_root.join("node_modules");
let entry_pkg = entry_nm.join(dep_name);
if entry_root.exists() {
std::fs::remove_dir_all(&entry_root)
.map_err(|e| miette!("inject: remove {}: {e}", entry_root.display()))?;
}
std::fs::create_dir_all(&entry_pkg)
.map_err(|e| miette!("inject: mkdir {}: {e}", entry_pkg.display()))?;
let files =
super::pack::collect_package_files(src_dir, &src_manifest).wrap_err_with(|| {
format!(
"inject: failed to enumerate {}'s packed files",
src_dir.display()
)
})?;
for (abs, rel) in &files {
let dst = entry_pkg.join(rel);
if let Some(parent) = dst.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| miette!("inject: mkdir {}: {e}", parent.display()))?;
}
std::fs::copy(abs, &dst).map_err(|e| {
miette!("inject: copy {} -> {}: {e}", abs.display(), dst.display())
})?;
}
if let Some(src_importer_path) = name_to_importer.get(dep_name)
&& let Some(direct_deps) = graph.importers.get(src_importer_path)
{
for direct in direct_deps {
if direct.name == *dep_name {
continue;
}
if let Some(sibling_ws_dir) = ws_dirs.get(&direct.name) {
create_symlink(&entry_nm, &direct.name, sibling_ws_dir)?;
continue;
}
let sibling_entry = aube_dir
.join(dep_path_to_filename(
&direct.dep_path,
virtual_store_dir_max_length,
))
.join("node_modules")
.join(&direct.name);
if !sibling_entry.exists() {
continue;
}
create_symlink(&entry_nm, &direct.name, &sibling_entry)?;
}
}
let consumer_dir = if importer_path == "." {
root_dir.to_path_buf()
} else {
root_dir.join(importer_path)
};
let top_link = consumer_dir.join(modules_dir_name).join(dep_name);
if top_link.symlink_metadata().is_ok() && std::fs::remove_file(&top_link).is_err() {
let _ = std::fs::remove_dir_all(&top_link);
}
if let Some(parent) = top_link.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| miette!("inject: mkdir {}: {e}", parent.display()))?;
}
create_symlink_exact(&top_link, &entry_pkg)?;
count += 1;
tracing::debug!("injected {dep_name}@{version} into {importer_path} as {entry_name}");
}
}
Ok(count)
}
fn short_hash(s: &str) -> String {
let digest = Sha256::digest(s.as_bytes());
let mut out = String::with_capacity(10);
for byte in digest.iter().take(5) {
use std::fmt::Write;
let _ = write!(&mut out, "{byte:02x}");
}
out
}
fn create_symlink(parent: &Path, name: &str, target: &Path) -> miette::Result<()> {
let link_path = parent.join(name);
if let Some(p) = link_path.parent() {
std::fs::create_dir_all(p).map_err(|e| miette!("inject: mkdir {}: {e}", p.display()))?;
}
if link_path.symlink_metadata().is_ok() {
std::fs::remove_file(&link_path)
.map_err(|e| miette!("inject: remove {}: {e}", link_path.display()))?;
}
create_symlink_exact(&link_path, target)
}
fn create_symlink_exact(link_path: &Path, target: &Path) -> miette::Result<()> {
let link_parent = link_path.parent().unwrap_or(Path::new(""));
let rel = pathdiff::diff_paths(target, link_parent).unwrap_or_else(|| target.to_path_buf());
aube_linker::create_dir_link(&rel, link_path)
.map_err(|e| miette!("inject: symlink {}: {e}", link_path.display()))
}