use super::*; use flate2::Compression; use flate2::write::GzEncoder; use pretty_assertions::assert_eq; use std::io::Cursor; use std::io::Write; #[cfg(unix)] #[test] fn materialize_npm_plugin_source_uses_packed_package_root() { use std::os::unix::fs::PermissionsExt; let codex_home = tempfile::tempdir().expect("create codex home"); let fake_npm_dir = tempfile::tempdir().expect("create fake npm directory"); let archive_bytes = npm_package_archive_bytes("@acme/plugin", "1.2.0").expect("build fixture archive"); let archive_path = fake_npm_dir.path().join("fixture.tgz"); fs::write(&archive_path, &archive_bytes).expect("write fixture archive"); let fake_npm = fake_npm_dir.path().join("npm"); fs::write( &fake_npm, format!( r#"#!/bin/sh destination="" previous="" for argument in "$@"; do if [ "$previous" = "--pack-destination" ]; then destination="$argument" fi previous="$argument" done cp "{}" "$destination/acme-plugin-1.2.0.tgz" printf '%s\n' "$@" > "$destination/args.txt" pwd > "$destination/pwd.txt" "#, archive_path.display() ), ) .expect("write fake npm"); let mut permissions = fs::metadata(&fake_npm) .expect("read fake npm metadata") .permissions(); permissions.set_mode(0o755); fs::set_permissions(&fake_npm, permissions).expect("make fake npm executable"); let (plugin_root, tempdir) = materialize_npm_plugin_source_with_command( codex_home.path(), "@acme/plugin", Some("^1.2.0"), Some("https://npm.example.com"), fake_npm.as_os_str(), ) .expect("materialize npm source"); assert_eq!( plugin_root.as_path(), tempdir.path().join("extracted/package") ); assert!( plugin_root .as_path() .join(".codex-plugin/plugin.json") .is_file() ); let args = fs::read_to_string(tempdir.path().join("args.txt")).expect("read npm arguments"); assert!(args.contains("pack")); assert!(args.contains("--ignore-scripts")); assert!(args.contains("--registry")); assert!(args.contains("https://npm.example.com")); assert!(args.contains("@acme/plugin@^1.2.0")); assert!(!args.contains("install")); let npm_working_directory = fs::canonicalize( fs::read_to_string(tempdir.path().join("pwd.txt")) .expect("read npm working directory") .trim(), ) .expect("canonicalize npm working directory"); assert_eq!( npm_working_directory, fs::canonicalize(tempdir.path()).expect("canonicalize tempdir") ); } fn npm_package_archive_bytes(package: &str, version: &str) -> std::io::Result> { let encoder = GzEncoder::new(Vec::new(), Compression::default()); let mut archive = tar::Builder::new(encoder); append_archive_file( &mut archive, "package/package.json", format!(r#"{{"name":"{package}","version":"{version}"}}"#).as_bytes(), )?; append_archive_file( &mut archive, "package/.codex-plugin/plugin.json", br#"{"name":"plugin"}"#, )?; let encoder = archive.into_inner()?; encoder.finish() } fn append_archive_file( archive: &mut tar::Builder, path: &str, contents: &[u8], ) -> std::io::Result<()> { let mut header = tar::Header::new_gnu(); header.set_size(contents.len() as u64); header.set_mode(0o644); header.set_cksum(); archive.append_data(&mut header, path, Cursor::new(contents)) }