fix(core): preserve zsh tied path exports with combined flags

Co-authored-by: Codex noreply@openai.com
This commit is contained in:
viyatb-oai
2026-05-28 18:14:37 -07:00
parent 8b1ea02bb3
commit c97f2eeadb
2 changed files with 45 additions and 39 deletions

View File

@@ -342,9 +342,10 @@ export_lines=$(export -p | awk '
line=$0
name=line
sub(/^(export|declare -x|typeset -x) /, "", name)
if (name ~ /^-T [A-Za-z_][A-Za-z0-9_]* [A-Za-z_][A-Za-z0-9_]*=/) {
# Zsh prints tied parameters like PATH/path as `export -T NAME tied=(...)`.
sub(/^-T /, "", name)
if (name ~ /^-[A-Za-z]*T[A-Za-z]* [A-Za-z_][A-Za-z0-9_]* [A-Za-z_][A-Za-z0-9_]*=/) {
# Zsh prints tied parameters like PATH/path as `export -T NAME tied=(...)`;
# the option group may include flags such as `-UT`.
sub(/^-[A-Za-z]*T[A-Za-z]* /, "", name)
sub(/ .*/, "", name)
} else {
sub(/=.*/, "", name)

View File

@@ -189,47 +189,52 @@ fn bash_snapshot_preserves_multiline_exports() -> Result<()> {
#[cfg(target_os = "macos")]
#[test]
fn zsh_snapshot_preserves_tied_path_export() -> Result<()> {
let dir = tempdir()?;
let tool_dir = dir.path().join("toolbin");
let expected_path = format!("{}:/usr/bin:/bin", tool_dir.display());
std::fs::write(
dir.path().join(".zshrc"),
format!("export PATH='{expected_path}'\n"),
)?;
fn zsh_snapshot_preserves_tied_path_exports() -> Result<()> {
for (zshrc_prefix, expected_export) in [
("", "export -T PATH path="),
("typeset -U PATH path\n", "export -UT PATH path="),
] {
let dir = tempdir()?;
let tool_dir = dir.path().join("toolbin");
let expected_path = format!("{}:/usr/bin:/bin", tool_dir.display());
std::fs::write(
dir.path().join(".zshrc"),
format!("{zshrc_prefix}export PATH='{expected_path}'\n"),
)?;
let output = Command::new("/bin/zsh")
.arg("-fc")
.arg(zsh_snapshot_script())
.env("ZDOTDIR", dir.path())
.output()?;
assert!(output.status.success());
let output = Command::new("/bin/zsh")
.arg("-fc")
.arg(zsh_snapshot_script())
.env("ZDOTDIR", dir.path())
.output()?;
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("export -T PATH path="),
"snapshot should include zsh tied PATH export; stdout={stdout:?}"
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains(expected_export),
"snapshot should include {expected_export:?}; stdout={stdout:?}"
);
let snapshot_path = dir.path().join("snapshot.sh");
std::fs::write(&snapshot_path, stdout.as_bytes())?;
let snapshot_path = dir.path().join("snapshot.sh");
std::fs::write(&snapshot_path, stdout.as_bytes())?;
let validate = Command::new("/bin/zsh")
.arg("-fc")
.arg("PATH=/should/not/survive; . \"$1\"; print -r -- \"$PATH\"")
.arg("zsh")
.arg(&snapshot_path)
.output()?;
assert!(
validate.status.success(),
"snapshot validation failed: {}",
String::from_utf8_lossy(&validate.stderr)
);
let validate = Command::new("/bin/zsh")
.arg("-fc")
.arg("PATH=/should/not/survive; . \"$1\"; print -r -- \"$PATH\"")
.arg("zsh")
.arg(&snapshot_path)
.output()?;
assert!(
validate.status.success(),
"snapshot validation failed: {}",
String::from_utf8_lossy(&validate.stderr)
);
assert_eq!(
String::from_utf8_lossy(&validate.stdout).trim(),
expected_path
);
assert_eq!(
String::from_utf8_lossy(&validate.stdout).trim(),
expected_path
);
}
Ok(())
}