diff --git a/codex-rs/install-context/src/bundle_tests.rs b/codex-rs/install-context/src/bundle_tests.rs new file mode 100644 index 0000000000..74a416639b --- /dev/null +++ b/codex-rs/install-context/src/bundle_tests.rs @@ -0,0 +1,48 @@ +//! Ensure a provisioned CLI still discovers its outer package and install method. + +use super::*; +use pretty_assertions::assert_eq; +use std::fs; + +#[test] +fn bundle_executable_preserves_package_layout_and_install_method() -> std::io::Result<()> { + let home = tempfile::tempdir()?; + let package = home.path().join("packages/standalone/releases/test"); + let executable = package.join("CodexCLI.app/Contents/MacOS/codex"); + fs::create_dir_all(executable.parent().unwrap())?; + fs::write(&executable, "")?; + for directory in [BIN_DIRNAME, RESOURCES_DIRNAME, PATH_DIRNAME] { + fs::create_dir_all(package.join(directory))?; + } + fs::write( + package.join(PACKAGE_METADATA_FILENAME), + r#"{"version":"1.2.3"}"#, + )?; + let package = canonical_absolute_path(&package).unwrap(); + let bin_dir = package.join(BIN_DIRNAME); + let resources_dir = package.join(RESOURCES_DIRNAME); + let path_dir = package.join(PATH_DIRNAME); + let context = InstallContext::from_exe_with_codex_home( + /*is_macos*/ true, + /*current_exe*/ Some(&executable), + /*method_override*/ None, + /*codex_home*/ Some(home.path()), + ); + assert_eq!( + context, + InstallContext { + method: InstallMethod::Standalone { + release_dir: package.clone(), + resources_dir: Some(resources_dir.clone()), + platform: standalone_platform(), + }, + package_layout: Some(CodexPackageLayout { + package_dir: package, + bin_dir, + resources_dir: Some(resources_dir), + path_dir: Some(path_dir), + }), + } + ); + Ok(()) +} diff --git a/codex-rs/install-context/src/lib.rs b/codex-rs/install-context/src/lib.rs index a88e2e2802..20f27515c8 100644 --- a/codex-rs/install-context/src/lib.rs +++ b/codex-rs/install-context/src/lib.rs @@ -248,6 +248,18 @@ impl CodexPackageLayout { let package_dir = exe_dir.parent()?; Self::from_package_bin_dir(package_dir.join(BIN_DIRNAME)) } + Some(name) if name == OsStr::new("MacOS") => { + // A provisioned CLI keeps helpers and metadata in the outer + // package. current_exe points inside the bundle, not at bin/codex. + let contents = exe_dir.parent()?; + let bundle = contents.parent()?; + if contents.file_name()? != OsStr::new("Contents") + || bundle.file_name()? != OsStr::new("CodexCLI.app") + { + return None; + } + Self::from_package_bin_dir(bundle.parent()?.join(BIN_DIRNAME)) + } Some(_) | None => None, } } @@ -344,6 +356,10 @@ fn zsh_resource_path() -> PathBuf { PathBuf::from(ZSH_DIRNAME).join(BIN_DIRNAME).join("zsh") } +#[cfg(test)] +#[path = "bundle_tests.rs"] +mod bundle_tests; + #[cfg(test)] mod tests { use super::*;