mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
cli: add package path from install context
This commit is contained in:
+8
-29
@@ -83,21 +83,14 @@ const legacyBinaryPath = (vendorRoot) =>
|
||||
path.join(vendorRoot, targetTriple, "codex", codexBinaryName);
|
||||
|
||||
function resolveNativePackage(vendorRoot) {
|
||||
const packageRoot = path.join(vendorRoot, targetTriple);
|
||||
const binaryPath = packageBinaryPath(vendorRoot);
|
||||
if (existsSync(binaryPath)) {
|
||||
return {
|
||||
binaryPath,
|
||||
pathDir: path.join(packageRoot, "codex-path"),
|
||||
};
|
||||
return binaryPath;
|
||||
}
|
||||
|
||||
const legacyPath = legacyBinaryPath(vendorRoot);
|
||||
if (existsSync(legacyPath)) {
|
||||
return {
|
||||
binaryPath: legacyPath,
|
||||
pathDir: path.join(packageRoot, "path"),
|
||||
};
|
||||
return legacyPath;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -124,7 +117,7 @@ if (!nativePackage) {
|
||||
);
|
||||
}
|
||||
|
||||
const { binaryPath, pathDir } = nativePackage;
|
||||
const binaryPath = nativePackage;
|
||||
|
||||
// Use an asynchronous spawn instead of spawnSync so that Node is able to
|
||||
// respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is
|
||||
@@ -132,16 +125,6 @@ const { binaryPath, pathDir } = nativePackage;
|
||||
// and guarantees that when either the child terminates or the parent
|
||||
// receives a fatal signal, both processes exit in a predictable manner.
|
||||
|
||||
function getUpdatedPath(newDirs) {
|
||||
const pathSep = process.platform === "win32" ? ";" : ":";
|
||||
const existingPath = process.env.PATH || "";
|
||||
const updatedPath = [
|
||||
...newDirs,
|
||||
...existingPath.split(pathSep).filter(Boolean),
|
||||
].join(pathSep);
|
||||
return updatedPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use heuristics to detect the package manager that was used to install Codex
|
||||
* in order to give the user a hint about how to update it.
|
||||
@@ -167,19 +150,15 @@ function detectPackageManager() {
|
||||
return userAgent ? "npm" : null;
|
||||
}
|
||||
|
||||
const additionalDirs = [];
|
||||
if (existsSync(pathDir)) {
|
||||
additionalDirs.push(pathDir);
|
||||
}
|
||||
const updatedPath = getUpdatedPath(additionalDirs);
|
||||
|
||||
const env = { ...process.env, PATH: updatedPath };
|
||||
const packageManagerEnvVar =
|
||||
detectPackageManager() === "bun"
|
||||
? "CODEX_MANAGED_BY_BUN"
|
||||
: "CODEX_MANAGED_BY_NPM";
|
||||
env[packageManagerEnvVar] = "1";
|
||||
env.CODEX_MANAGED_PACKAGE_ROOT = realpathSync(path.join(__dirname, ".."));
|
||||
const env = {
|
||||
...process.env,
|
||||
[packageManagerEnvVar]: "1",
|
||||
CODEX_MANAGED_PACKAGE_ROOT: realpathSync(path.join(__dirname, "..")),
|
||||
};
|
||||
|
||||
const child = spawn(binaryPath, process.argv.slice(2), {
|
||||
stdio: "inherit",
|
||||
|
||||
Generated
+2
@@ -2145,12 +2145,14 @@ dependencies = [
|
||||
"anyhow",
|
||||
"codex-apply-patch",
|
||||
"codex-exec-server",
|
||||
"codex-install-context",
|
||||
"codex-linux-sandbox",
|
||||
"codex-sandboxing",
|
||||
"codex-shell-escalation",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-home-dir",
|
||||
"dotenvy",
|
||||
"pretty_assertions",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -16,6 +16,7 @@ workspace = true
|
||||
anyhow = { workspace = true }
|
||||
codex-apply-patch = { workspace = true }
|
||||
codex-exec-server = { workspace = true }
|
||||
codex-install-context = { workspace = true }
|
||||
codex-linux-sandbox = { workspace = true }
|
||||
codex-sandboxing = { workspace = true }
|
||||
codex-shell-escalation = { workspace = true }
|
||||
@@ -24,3 +25,6 @@ codex-utils-home-dir = { workspace = true }
|
||||
dotenvy = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
|
||||
+91
-20
@@ -1,3 +1,4 @@
|
||||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::future::Future;
|
||||
use std::path::Path;
|
||||
@@ -5,6 +6,7 @@ use std::path::PathBuf;
|
||||
|
||||
use codex_apply_patch::CODEX_CORE_APPLY_PATCH_ARG1;
|
||||
use codex_exec_server::CODEX_FS_HELPER_ARG1;
|
||||
use codex_install_context::InstallContext;
|
||||
use codex_sandboxing::landlock::CODEX_LINUX_SANDBOX_ARG0;
|
||||
use codex_utils_home_dir::find_codex_home;
|
||||
#[cfg(unix)]
|
||||
@@ -137,6 +139,7 @@ pub fn arg0_dispatch() -> Option<Arg0PathEntryGuard> {
|
||||
// This modifies the environment, which is not thread-safe, so do this
|
||||
// before creating any threads/the Tokio runtime.
|
||||
load_dotenv();
|
||||
prepend_package_path_dir();
|
||||
|
||||
match prepend_path_entry_for_codex_aliases() {
|
||||
Ok(path_entry) => Some(path_entry),
|
||||
@@ -286,9 +289,8 @@ where
|
||||
/// with the hidden `--codex-run-as-apply-patch` flag.
|
||||
///
|
||||
/// This temporary directory is prepended to the PATH environment variable so
|
||||
/// that `apply_patch` can be on the PATH without requiring the user to
|
||||
/// install a separate `apply_patch` executable, simplifying the deployment of
|
||||
/// Codex CLI.
|
||||
/// that `apply_patch` can be on the PATH without requiring the user to install
|
||||
/// a separate executable, simplifying the deployment of Codex CLI.
|
||||
/// Note: In debug builds the temp-dir guard is disabled to ease local testing.
|
||||
///
|
||||
/// IMPORTANT: This function modifies the PATH environment variable, so it MUST
|
||||
@@ -371,23 +373,7 @@ pub fn prepend_path_entry_for_codex_aliases() -> std::io::Result<Arg0PathEntryGu
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
const PATH_SEPARATOR: &str = ":";
|
||||
|
||||
#[cfg(windows)]
|
||||
const PATH_SEPARATOR: &str = ";";
|
||||
|
||||
let updated_path_env_var = match std::env::var_os("PATH") {
|
||||
Some(existing_path) => {
|
||||
let mut path_env_var =
|
||||
std::ffi::OsString::with_capacity(path.as_os_str().len() + 1 + existing_path.len());
|
||||
path_env_var.push(path);
|
||||
path_env_var.push(PATH_SEPARATOR);
|
||||
path_env_var.push(existing_path);
|
||||
path_env_var
|
||||
}
|
||||
None => path.as_os_str().to_owned(),
|
||||
};
|
||||
let updated_path_env_var = path_env_with_entry(path, std::env::var_os("PATH"));
|
||||
|
||||
unsafe {
|
||||
std::env::set_var("PATH", updated_path_env_var);
|
||||
@@ -420,6 +406,49 @@ pub fn prepend_path_entry_for_codex_aliases() -> std::io::Result<Arg0PathEntryGu
|
||||
Ok(Arg0PathEntryGuard::new(temp_dir, lock_file, paths))
|
||||
}
|
||||
|
||||
fn prepend_package_path_dir() {
|
||||
let Some(updated_path_env_var) =
|
||||
path_env_with_package_path_dir(InstallContext::current(), std::env::var_os("PATH"))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
unsafe {
|
||||
std::env::set_var("PATH", updated_path_env_var);
|
||||
}
|
||||
}
|
||||
|
||||
fn path_env_with_package_path_dir(
|
||||
install_context: &InstallContext,
|
||||
existing_path: Option<OsString>,
|
||||
) -> Option<OsString> {
|
||||
let path_dir = install_context
|
||||
.package_layout
|
||||
.as_ref()
|
||||
.and_then(|package_layout| package_layout.path_dir.as_ref())?;
|
||||
Some(path_env_with_entry(path_dir.as_path(), existing_path))
|
||||
}
|
||||
|
||||
fn path_env_with_entry(path_entry: &Path, existing_path: Option<OsString>) -> OsString {
|
||||
#[cfg(unix)]
|
||||
const PATH_SEPARATOR: &str = ":";
|
||||
|
||||
#[cfg(windows)]
|
||||
const PATH_SEPARATOR: &str = ";";
|
||||
|
||||
let capacity = path_entry.as_os_str().len()
|
||||
+ existing_path
|
||||
.as_ref()
|
||||
.map_or(0, |existing_path| 1 + existing_path.len());
|
||||
let mut path_env_var = OsString::with_capacity(capacity);
|
||||
path_env_var.push(path_entry);
|
||||
if let Some(existing_path) = existing_path {
|
||||
path_env_var.push(PATH_SEPARATOR);
|
||||
path_env_var.push(existing_path);
|
||||
}
|
||||
path_env_var
|
||||
}
|
||||
|
||||
fn janitor_cleanup(temp_root: &Path) -> std::io::Result<()> {
|
||||
let entries = match std::fs::read_dir(temp_root) {
|
||||
Ok(entries) => entries,
|
||||
@@ -475,6 +504,11 @@ mod tests {
|
||||
use super::run_main_with_arg0_guard;
|
||||
#[cfg(unix)]
|
||||
use anyhow::ensure;
|
||||
use codex_install_context::CodexPackageLayout;
|
||||
use codex_install_context::InstallContext;
|
||||
use codex_install_context::InstallMethod;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
@@ -513,6 +547,43 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_env_can_prepend_package_path_before_arg0_alias_dir() -> anyhow::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let arg0_dir = temp_dir.path().join("arg0");
|
||||
let package_dir = temp_dir.path().join("package");
|
||||
let bin_dir = package_dir.join("bin");
|
||||
let path_dir = package_dir.join("codex-path");
|
||||
let existing_dir = temp_dir.path().join("existing-bin");
|
||||
fs::create_dir_all(&arg0_dir)?;
|
||||
fs::create_dir_all(&bin_dir)?;
|
||||
fs::create_dir_all(&path_dir)?;
|
||||
fs::create_dir_all(&existing_dir)?;
|
||||
let path_dir = AbsolutePathBuf::from_absolute_path(path_dir.canonicalize()?)?;
|
||||
let install_context = InstallContext {
|
||||
method: InstallMethod::Other,
|
||||
package_layout: Some(CodexPackageLayout {
|
||||
package_dir: AbsolutePathBuf::from_absolute_path(package_dir.canonicalize()?)?,
|
||||
bin_dir: AbsolutePathBuf::from_absolute_path(bin_dir.canonicalize()?)?,
|
||||
resources_dir: None,
|
||||
path_dir: Some(path_dir.clone()),
|
||||
}),
|
||||
};
|
||||
|
||||
let package_path = super::path_env_with_package_path_dir(
|
||||
&install_context,
|
||||
Some(existing_dir.as_os_str().to_owned()),
|
||||
)
|
||||
.expect("package path dir should update PATH");
|
||||
let updated_path = super::path_env_with_entry(&arg0_dir, Some(package_path));
|
||||
|
||||
assert_eq!(
|
||||
std::env::split_paths(&updated_path).collect::<Vec<_>>(),
|
||||
vec![arg0_dir, path_dir.as_path().to_path_buf(), existing_dir,],
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn run_main_with_arg0_guard_keeps_aliases_alive_until_main_returns() -> anyhow::Result<()> {
|
||||
|
||||
@@ -10,6 +10,8 @@ use crate::sandboxing::SandboxPermissions;
|
||||
use crate::shell::Shell;
|
||||
use crate::shell::ShellType;
|
||||
use crate::tools::sandboxing::ToolError;
|
||||
#[cfg(unix)]
|
||||
use codex_install_context::InstallContext;
|
||||
#[cfg(target_os = "macos")]
|
||||
use codex_network_proxy::CODEX_PROXY_GIT_SSH_COMMAND_MARKER;
|
||||
use codex_network_proxy::CUSTOM_CA_ENV_KEYS;
|
||||
@@ -99,6 +101,35 @@ fn prepend_path_entry(env: &mut HashMap<String, String>, path_entry: &str) -> St
|
||||
updated_path
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn apply_package_path_prepend(
|
||||
env: &mut HashMap<String, String>,
|
||||
explicit_env_overrides: &mut HashMap<String, String>,
|
||||
) {
|
||||
let Some(path_dir) = InstallContext::current()
|
||||
.package_layout
|
||||
.as_ref()
|
||||
.and_then(|package_layout| package_layout.path_dir.as_ref())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
apply_path_prepend(env, explicit_env_overrides, path_dir.as_path());
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn apply_path_prepend(
|
||||
env: &mut HashMap<String, String>,
|
||||
explicit_env_overrides: &mut HashMap<String, String>,
|
||||
path_entry: &Path,
|
||||
) {
|
||||
let path_entry = path_entry.to_string_lossy();
|
||||
let updated_path = prepend_path_entry(env, path_entry.as_ref());
|
||||
// Snapshot wrapping restores explicit overrides after sourcing the shell
|
||||
// snapshot, so capture this PATH override there as well.
|
||||
explicit_env_overrides.insert("PATH".to_string(), updated_path);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn prepend_zsh_fork_bin_to_path(
|
||||
env: &mut HashMap<String, String>,
|
||||
@@ -116,12 +147,10 @@ pub(crate) fn apply_zsh_fork_path_prepend(
|
||||
explicit_env_overrides: &mut HashMap<String, String>,
|
||||
shell_zsh_path: &Path,
|
||||
) {
|
||||
let Some(updated_path) = prepend_zsh_fork_bin_to_path(env, shell_zsh_path) else {
|
||||
let Some(zsh_bin_dir) = shell_zsh_path.parent() else {
|
||||
return;
|
||||
};
|
||||
// Snapshot wrapping restores explicit overrides after sourcing the shell
|
||||
// snapshot, so capture this PATH override there as well.
|
||||
explicit_env_overrides.insert("PATH".to_string(), updated_path);
|
||||
apply_path_prepend(env, explicit_env_overrides, zsh_bin_dir);
|
||||
}
|
||||
|
||||
pub(crate) fn disable_powershell_profile_for_elevated_windows_sandbox(
|
||||
|
||||
@@ -166,6 +166,26 @@ fn explicit_escalation_preserves_user_ca_env() {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn apply_path_prepend_records_explicit_path_override() {
|
||||
let mut env = HashMap::from([("PATH".to_string(), "/usr/bin:/bin".to_string())]);
|
||||
let mut explicit_env_overrides = HashMap::new();
|
||||
|
||||
apply_path_prepend(
|
||||
&mut env,
|
||||
&mut explicit_env_overrides,
|
||||
PathBuf::from("/package/codex-path").as_path(),
|
||||
);
|
||||
|
||||
let expected = "/package/codex-path:/usr/bin:/bin";
|
||||
assert_eq!(env.get("PATH").map(String::as_str), Some(expected));
|
||||
assert_eq!(
|
||||
explicit_env_overrides.get("PATH").map(String::as_str),
|
||||
Some(expected)
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn apply_zsh_fork_path_prepend_uses_shell_parent() {
|
||||
@@ -883,6 +903,55 @@ fn maybe_wrap_shell_lc_with_snapshot_applies_explicit_path_override() {
|
||||
assert_eq!(String::from_utf8_lossy(&output.stdout), "/worktree/bin");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn maybe_wrap_shell_lc_with_snapshot_preserves_package_path_prepend() {
|
||||
let dir = tempdir().expect("create temp dir");
|
||||
let snapshot_path = dir.path().join("snapshot.sh");
|
||||
std::fs::write(
|
||||
&snapshot_path,
|
||||
"# Snapshot file\nexport PATH='/snapshot/bin'\n",
|
||||
)
|
||||
.expect("write snapshot");
|
||||
let session_shell = shell_with_snapshot(
|
||||
ShellType::Bash,
|
||||
"/bin/bash",
|
||||
snapshot_path.abs(),
|
||||
dir.path().abs(),
|
||||
);
|
||||
let command = vec![
|
||||
"/bin/bash".to_string(),
|
||||
"-lc".to_string(),
|
||||
"printf '%s' \"$PATH\"".to_string(),
|
||||
];
|
||||
let package_path_dir = dir.path().join("codex-path");
|
||||
let mut env = HashMap::from([("PATH".to_string(), "/worktree/bin".to_string())]);
|
||||
let mut explicit_env_overrides = HashMap::new();
|
||||
apply_path_prepend(
|
||||
&mut env,
|
||||
&mut explicit_env_overrides,
|
||||
package_path_dir.as_path(),
|
||||
);
|
||||
let rewritten = maybe_wrap_shell_lc_with_snapshot(
|
||||
&command,
|
||||
&session_shell,
|
||||
&dir.path().abs(),
|
||||
&explicit_env_overrides,
|
||||
&env,
|
||||
);
|
||||
let output = Command::new(&rewritten[0])
|
||||
.args(&rewritten[1..])
|
||||
.env("PATH", env.get("PATH").expect("PATH should be set"))
|
||||
.output()
|
||||
.expect("run rewritten command");
|
||||
|
||||
assert!(output.status.success(), "command failed: {output:?}");
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
format!("{}:/worktree/bin", package_path_dir.display())
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn maybe_wrap_shell_lc_with_snapshot_preserves_zsh_fork_path_prepend() {
|
||||
|
||||
@@ -21,6 +21,8 @@ use crate::tools::flat_tool_name;
|
||||
use crate::tools::network_approval::NetworkApprovalMode;
|
||||
use crate::tools::network_approval::NetworkApprovalSpec;
|
||||
#[cfg(unix)]
|
||||
use crate::tools::runtimes::apply_package_path_prepend;
|
||||
#[cfg(unix)]
|
||||
use crate::tools::runtimes::apply_zsh_fork_path_prepend;
|
||||
use crate::tools::runtimes::build_sandbox_command;
|
||||
use crate::tools::runtimes::disable_powershell_profile_for_elevated_windows_sandbox;
|
||||
@@ -252,6 +254,7 @@ impl ToolRuntime<ShellRequest, ExecToolCallOutput> for ShellRuntime {
|
||||
let (env, explicit_env_overrides) = {
|
||||
let mut env = env;
|
||||
let mut explicit_env_overrides = explicit_env_overrides;
|
||||
apply_package_path_prepend(&mut env, &mut explicit_env_overrides);
|
||||
if self.backend == ShellRuntimeBackend::ShellCommandZshFork
|
||||
&& let Some(shell_zsh_path) = ctx.session.services.shell_zsh_path.as_deref()
|
||||
{
|
||||
|
||||
@@ -18,6 +18,8 @@ use crate::tools::flat_tool_name;
|
||||
use crate::tools::network_approval::NetworkApprovalMode;
|
||||
use crate::tools::network_approval::NetworkApprovalSpec;
|
||||
#[cfg(unix)]
|
||||
use crate::tools::runtimes::apply_package_path_prepend;
|
||||
#[cfg(unix)]
|
||||
use crate::tools::runtimes::apply_zsh_fork_path_prepend;
|
||||
use crate::tools::runtimes::build_sandbox_command;
|
||||
use crate::tools::runtimes::disable_powershell_profile_for_elevated_windows_sandbox;
|
||||
@@ -282,6 +284,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
|
||||
#[cfg(unix)]
|
||||
let explicit_env_overrides = {
|
||||
let mut explicit_env_overrides = explicit_env_overrides;
|
||||
apply_package_path_prepend(&mut env, &mut explicit_env_overrides);
|
||||
if let UnifiedExecShellMode::ZshFork(zsh_fork_config) = &self.shell_mode {
|
||||
apply_zsh_fork_path_prepend(
|
||||
&mut env,
|
||||
|
||||
@@ -6,6 +6,8 @@ use std::sync::OnceLock;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
const BIN_DIRNAME: &str = "bin";
|
||||
const LEGACY_BINARY_DIRNAME: &str = "codex";
|
||||
const LEGACY_PATH_DIRNAME: &str = "path";
|
||||
const PACKAGE_METADATA_FILENAME: &str = "codex-package.json";
|
||||
const PATH_DIRNAME: &str = "codex-path";
|
||||
const RELEASES_DIRNAME: &str = "releases";
|
||||
@@ -89,7 +91,9 @@ impl InstallContext {
|
||||
managed_by_bun: bool,
|
||||
codex_home: Option<&Path>,
|
||||
) -> Self {
|
||||
let package_layout = current_exe.and_then(CodexPackageLayout::from_exe);
|
||||
let allow_legacy_layout = managed_by_npm || managed_by_bun;
|
||||
let package_layout = current_exe
|
||||
.and_then(|exe_path| CodexPackageLayout::from_exe(exe_path, allow_legacy_layout));
|
||||
let method = if managed_by_npm {
|
||||
InstallMethod::Npm
|
||||
} else if managed_by_bun {
|
||||
@@ -182,13 +186,19 @@ impl InstallContext {
|
||||
}
|
||||
|
||||
impl CodexPackageLayout {
|
||||
fn from_exe(exe_path: &Path) -> Option<Self> {
|
||||
fn from_exe(exe_path: &Path, allow_legacy_layout: bool) -> Option<Self> {
|
||||
let canonical_exe = canonical_absolute_path(exe_path)?;
|
||||
let bin_dir = canonical_exe.parent()?;
|
||||
if bin_dir.file_name() != Some(OsStr::new(BIN_DIRNAME)) {
|
||||
return None;
|
||||
let exe_dir = canonical_exe.parent()?;
|
||||
match exe_dir.file_name() {
|
||||
Some(name) if name == OsStr::new(BIN_DIRNAME) => Self::from_package_bin_dir(exe_dir),
|
||||
Some(name) if allow_legacy_layout && name == OsStr::new(LEGACY_BINARY_DIRNAME) => {
|
||||
Self::from_legacy_binary_dir(exe_dir)
|
||||
}
|
||||
Some(_) | None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_package_bin_dir(bin_dir: AbsolutePathBuf) -> Option<Self> {
|
||||
let package_dir = bin_dir.parent()?;
|
||||
if !package_dir.join(PACKAGE_METADATA_FILENAME).is_file() {
|
||||
return None;
|
||||
@@ -201,6 +211,18 @@ impl CodexPackageLayout {
|
||||
bin_dir,
|
||||
})
|
||||
}
|
||||
|
||||
fn from_legacy_binary_dir(bin_dir: AbsolutePathBuf) -> Option<Self> {
|
||||
let package_dir = bin_dir.parent()?;
|
||||
let path_dir = existing_dir(package_dir.join(LEGACY_PATH_DIRNAME))?;
|
||||
|
||||
Some(Self {
|
||||
resources_dir: existing_dir(package_dir.join(RESOURCES_DIRNAME)),
|
||||
path_dir: Some(path_dir),
|
||||
package_dir,
|
||||
bin_dir,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn install_method_from_exe(
|
||||
@@ -511,6 +533,103 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn npm_managed_legacy_package_keeps_path_dir() -> std::io::Result<()> {
|
||||
let package_dir = tempfile::tempdir()?;
|
||||
let bin_dir = package_dir.path().join(LEGACY_BINARY_DIRNAME);
|
||||
let path_dir = package_dir.path().join(LEGACY_PATH_DIRNAME);
|
||||
fs::create_dir_all(&bin_dir)?;
|
||||
fs::create_dir_all(&path_dir)?;
|
||||
let exe_path = bin_dir.join(if cfg!(windows) { "codex.exe" } else { "codex" });
|
||||
fs::write(&exe_path, "")?;
|
||||
fs::write(path_dir.join(default_rg_command()), "")?;
|
||||
let canonical_package_dir =
|
||||
AbsolutePathBuf::from_absolute_path(package_dir.path().canonicalize()?)?;
|
||||
let canonical_bin_dir = AbsolutePathBuf::from_absolute_path(bin_dir.canonicalize()?)?;
|
||||
let canonical_path_dir = AbsolutePathBuf::from_absolute_path(path_dir.canonicalize()?)?;
|
||||
|
||||
let context = InstallContext::from_exe_with_codex_home(
|
||||
/*is_macos*/ false,
|
||||
/*current_exe*/ Some(&exe_path),
|
||||
/*managed_by_npm*/ true,
|
||||
/*managed_by_bun*/ false,
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
assert_eq!(
|
||||
context,
|
||||
InstallContext {
|
||||
method: InstallMethod::Npm,
|
||||
package_layout: Some(CodexPackageLayout {
|
||||
package_dir: canonical_package_dir,
|
||||
bin_dir: canonical_bin_dir,
|
||||
resources_dir: None,
|
||||
path_dir: Some(canonical_path_dir.clone()),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
context.rg_command(),
|
||||
canonical_path_dir
|
||||
.join(default_rg_command())
|
||||
.into_path_buf()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_package_layout_requires_path_dir() -> std::io::Result<()> {
|
||||
let package_dir = tempfile::tempdir()?;
|
||||
let bin_dir = package_dir.path().join(LEGACY_BINARY_DIRNAME);
|
||||
fs::create_dir_all(&bin_dir)?;
|
||||
let exe_path = bin_dir.join(if cfg!(windows) { "codex.exe" } else { "codex" });
|
||||
fs::write(&exe_path, "")?;
|
||||
|
||||
let context = InstallContext::from_exe_with_codex_home(
|
||||
/*is_macos*/ false,
|
||||
/*current_exe*/ Some(&exe_path),
|
||||
/*managed_by_npm*/ true,
|
||||
/*managed_by_bun*/ false,
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
assert_eq!(
|
||||
context,
|
||||
InstallContext {
|
||||
method: InstallMethod::Npm,
|
||||
package_layout: None,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unmanaged_legacy_shaped_package_ignores_path_dir() -> std::io::Result<()> {
|
||||
let package_dir = tempfile::tempdir()?;
|
||||
let bin_dir = package_dir.path().join(LEGACY_BINARY_DIRNAME);
|
||||
let path_dir = package_dir.path().join(LEGACY_PATH_DIRNAME);
|
||||
fs::create_dir_all(&bin_dir)?;
|
||||
fs::create_dir_all(&path_dir)?;
|
||||
let exe_path = bin_dir.join(if cfg!(windows) { "codex.exe" } else { "codex" });
|
||||
fs::write(&exe_path, "")?;
|
||||
fs::write(path_dir.join(default_rg_command()), "")?;
|
||||
|
||||
let context = InstallContext::from_exe_with_codex_home(
|
||||
/*is_macos*/ false,
|
||||
/*current_exe*/ Some(&exe_path),
|
||||
/*managed_by_npm*/ false,
|
||||
/*managed_by_bun*/ false,
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
assert_eq!(
|
||||
context,
|
||||
InstallContext {
|
||||
method: InstallMethod::Other,
|
||||
package_layout: None,
|
||||
}
|
||||
);
|
||||
assert_eq!(context.rg_command(), default_rg_command());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn standalone_package_rg_falls_back_when_codex_path_is_missing() -> std::io::Result<()> {
|
||||
let package_dir = tempfile::tempdir()?;
|
||||
|
||||
Reference in New Issue
Block a user