use super::*;
use crate::core::backend::{BuildConfig, BuildDependency};
use std::io::Write as _;
use std::path::Path;
fn write_file(path: &Path, contents: &str) {
std::fs::create_dir_all(path.parent().expect("fixture path must have a parent")).expect("create fixture dir");
let mut file = std::fs::File::create(path).expect("create fixture file");
file.write_all(contents.as_bytes()).expect("write fixture file");
}
fn npx_is_runnable() -> bool {
static RUNNABLE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*RUNNABLE.get_or_init(|| {
std::process::Command::new("npx")
.arg("--version")
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok_and(|status| status.success())
})
}
#[test]
fn napi_build_bakes_the_crate_local_package_name_not_the_workspace_roots() {
if !npx_is_runnable() {
return;
}
let project = tempfile::tempdir().expect("create tempdir");
let root = project.path();
write_file(
&root.join("package.json"),
r#"{"name":"workspace-root-pkg","version":"0.0.0","private":true,"napi":{"binaryName":"workspace-root-pkg"}}"#,
);
let alef_cfg: crate::core::config::NewAlefConfig = toml::from_str(
r#"
[workspace]
languages = ["node"]
[[crates]]
name = "mylib"
sources = ["src/lib.rs"]
"#,
)
.expect("parse fixture alef.toml");
let config = alef_cfg.resolve().expect("resolve fixture config").remove(0);
let build_config = BuildConfig {
tool: "napi",
crate_suffix: "-node",
build_dep: BuildDependency::None,
post_build: Vec::new(),
};
let command = build_command_for(Language::Node, &build_config, &config, false);
let crate_dir = root.join("crates/mylib-node");
write_file(
&crate_dir.join("Cargo.toml"),
"[package]\nname = \"mylib-node\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[lib]\ncrate-type = \
[\"cdylib\"]\n\n[dependencies]\nnapi = { version = \"3\", default-features = false, features = \
[\"napi9\"] }\nnapi-derive = \"3\"\n\n[build-dependencies]\nnapi-build = \"2\"\n",
);
write_file(
&crate_dir.join("build.rs"),
"fn main() {\n napi_build::setup();\n}\n",
);
write_file(
&crate_dir.join("src/lib.rs"),
"#[macro_use]\nextern crate napi_derive;\n\n#[napi]\npub fn add(a: i32, b: i32) -> i32 {\n a + b\n}\n",
);
write_file(
&crate_dir.join("package.json"),
r#"{"name":"crate-local-pkg","version":"0.1.0","napi":{"binaryName":"crate-local-pkg","targets":["x86_64-apple-darwin","aarch64-apple-darwin","x86_64-unknown-linux-gnu"]}}"#,
);
let status = std::process::Command::new("sh")
.arg("-c")
.arg(&command)
.current_dir(root)
.status()
.expect("spawn napi build");
assert!(status.success(), "napi build must succeed: {command}");
let node_artifact = std::fs::read_dir(&crate_dir)
.expect("read crate dir")
.filter_map(Result::ok)
.map(|entry| entry.file_name().to_string_lossy().into_owned())
.find(|name| name.ends_with(".node"))
.unwrap_or_else(|| {
panic!(
"napi build must still produce a native .node artifact in {}",
crate_dir.display()
)
});
assert!(
node_artifact.starts_with("crate-local-pkg."),
"the emitted native artifact must be named after the crate-local package.json's \
napi.binaryName, not the workspace root's: got {node_artifact}"
);
assert!(
!node_artifact.starts_with("workspace-root-pkg."),
"the emitted native artifact must never bake in the workspace-root package.json's \
napi.binaryName -- exactly the alef#368 defect: got {node_artifact}"
);
}