Detect standalone installs from the macOS CLI bundle (#42068)

## What changed

- Recognize executables under `CodexCLI.app/Contents/MacOS` and resolve the
  surrounding standalone package layout from its sibling `bin` directory.
- Preserve standalone install detection and access to bundled resources and
  path entries when Codex is launched through the app bundle.

## Testing

- Add a regression test for package layout and install method discovery from a
  bundled executable.

GitOrigin-RevId: 02b41a978b256da43df95a922bc5ee99a78b8168
This commit is contained in:
Eric Burke
2026-09-01 14:51:54 +00:00
committed by copyberry
parent 25ba0ae6cd
commit 3c6bb8b3eb
2 changed files with 64 additions and 0 deletions
@@ -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(())
}
+16
View File
@@ -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::*;