Bug wsl terminal zsh shell ready wrapper exports (#8025)
* Fix WSL zsh ZDOTDIR restore using runtime-sourced path instead of baked Windows-generated shell-ready wrappers are sourced via /mnt/c on WSL, where the generation-time path baked into .zshenv doesn't exist. Capture the runtime ZDOTDIR before it's unset and prefer it (when it still points to a valid wrapper dir) over the baked literal when restoring ZDOTDIR, so user .zshrc/.zshenv files load correctly under WSL. * Fix zsh ZDOTDIR self-restore to guard against unset variables Add ":-" default expansion when referencing _orca_wrapper_zdotdir_self so the wrapper doesn't abort under `set -u` when the variable is unset, and extend the shell-ready test to assert login-shell behavior (IS_LOGIN) and improve failure diagnostics on zsh spawn.
This commit is contained in:
parent
4908a03671
commit
620b475acd
|
|
@ -5,7 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|||
import { spawnSync } from 'node:child_process'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join, dirname } from 'node:path'
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync } from 'node:fs'
|
||||
import { mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync, mkdirSync } from 'node:fs'
|
||||
import type * as pty from 'node-pty'
|
||||
import type * as LocalPtyShellReadyModule from './local-pty-shell-ready'
|
||||
import {
|
||||
|
|
@ -724,6 +724,31 @@ describePosix('local PTY shell-ready launch config', () => {
|
|||
// Fallback chain: discovered → normalized spawn-env path → HOME
|
||||
expect(zshenv).toContain('${_orca_discovered_zdotdir:-${_orca_user_zdotdir:-$HOME}}')
|
||||
})
|
||||
|
||||
it('restores wrapper ZDOTDIR from the runtime sourced path, not the baked literal', async () => {
|
||||
// Why: issue #8003 — WSL sources Windows-generated wrappers via /mnt/c,
|
||||
// so the generation-time path baked into .zshenv does not exist there.
|
||||
const { getShellReadyLaunchConfig } = await importFreshLocalPtyShellReady()
|
||||
|
||||
getShellReadyLaunchConfig('/bin/zsh')
|
||||
|
||||
const zshenv = readFileSync(join(userDataPath, 'shell-ready', 'zsh', '.zshenv'), 'utf8')
|
||||
|
||||
expect(zshenv).toContain('_orca_wrapper_zdotdir_self="${ZDOTDIR:-}"')
|
||||
// The runtime path is only trusted when it still holds a wrapper .zshenv;
|
||||
// otherwise the generation-time literal remains as the fallback.
|
||||
expect(zshenv).toContain(
|
||||
'if [[ -n "${_orca_wrapper_zdotdir_self:-}" && -f "${_orca_wrapper_zdotdir_self:-}/.zshenv" ]]; then\n' +
|
||||
' export ZDOTDIR="${_orca_wrapper_zdotdir_self:-}"\n' +
|
||||
'else\n' +
|
||||
` export ZDOTDIR='${join(userDataPath, 'shell-ready', 'zsh')}'\n` +
|
||||
'fi'
|
||||
)
|
||||
// Capture must happen before the wrapper unsets ZDOTDIR to source user files.
|
||||
expect(zshenv.indexOf('_orca_wrapper_zdotdir_self="${ZDOTDIR:-}"')).toBeLessThan(
|
||||
zshenv.indexOf('unset ZDOTDIR')
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// Why: end-to-end validation that wrapper ZDOTDIR discovery preserves top-level
|
||||
|
|
@ -812,6 +837,59 @@ path=(/custom/bin $path)
|
|||
expect(output).toContain('PATH_HAS_CUSTOM=/custom/bin')
|
||||
})
|
||||
|
||||
it('loads user .zshrc when wrappers are sourced from a different runtime path (WSL simulation)', async () => {
|
||||
// Why: issue #8003 — on Windows the wrappers are generated under the
|
||||
// native userData path but WSL sources them via /mnt/c, where the baked
|
||||
// generation-time path does not exist. Renaming the userData dir after
|
||||
// generation reproduces that split: runtime ZDOTDIR resolves, the baked
|
||||
// literal does not.
|
||||
writeFileSync(join(testHome, '.zshrc'), 'export USER_ZSHRC_LOADED=yes\n')
|
||||
|
||||
const { getShellReadyLaunchConfig } = await importFreshLocalPtyShellReady()
|
||||
getShellReadyLaunchConfig('/bin/zsh')
|
||||
|
||||
const movedUserData = `${userDataPath}-wsl-view`
|
||||
renameSync(userDataPath, movedUserData)
|
||||
try {
|
||||
const cleanEnv: Record<string, string | undefined> = {
|
||||
...process.env,
|
||||
HOME: testHome,
|
||||
PATH: '/usr/bin:/bin'
|
||||
}
|
||||
delete cleanEnv.ZDOTDIR
|
||||
delete cleanEnv.ORCA_ORIG_ZDOTDIR
|
||||
delete cleanEnv.ORCA_ATTRIBUTION_SHIM_DIR
|
||||
delete cleanEnv.USER_ZSHRC_LOADED
|
||||
cleanEnv.ZDOTDIR = join(movedUserData, 'shell-ready', 'zsh')
|
||||
|
||||
// Production WSL launches a login shell (`exec zsh -l`); also cover the
|
||||
// non-login flow used by local panes so both restore paths stay pinned.
|
||||
// Login must still load user .zshrc (via wrapper .zshrc after .zprofile)
|
||||
// and leave final ZDOTDIR at the user home after .zlogin restore.
|
||||
for (const args of [['-i'], ['-l', '-i']] as const) {
|
||||
const result = spawnSync(
|
||||
'zsh',
|
||||
[
|
||||
...args,
|
||||
'-c',
|
||||
'echo "USER_ZSHRC_LOADED=${USER_ZSHRC_LOADED:-no}" && echo "FINAL_ZDOTDIR=${ZDOTDIR:-unset}" && echo "IS_LOGIN=$([[ -o login ]] && echo yes || echo no)"'
|
||||
],
|
||||
{
|
||||
env: cleanEnv as NodeJS.ProcessEnv,
|
||||
encoding: 'utf8'
|
||||
}
|
||||
)
|
||||
|
||||
expect(result.status, `zsh ${args.join(' ')} failed: ${result.stderr}`).toBe(0)
|
||||
expect(result.stdout).toContain('USER_ZSHRC_LOADED=yes')
|
||||
expect(result.stdout).toContain(`FINAL_ZDOTDIR=${testHome}`)
|
||||
expect(result.stdout).toContain(args.includes('-l') ? 'IS_LOGIN=yes' : 'IS_LOGIN=no')
|
||||
}
|
||||
} finally {
|
||||
rmSync(movedUserData, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('preserves top-level .zshenv path and function side effects', async () => {
|
||||
// Why: .zshenv is the normal place for always-on zsh env/path setup.
|
||||
// Dropping those side effects regresses non-Orca zsh startup semantics.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,13 @@ export function getZshEnvTemplate(zshDir: string, headerPrefix = ''): string {
|
|||
? `Orca ${headerPrefix} zsh shell-ready wrapper`
|
||||
: 'Orca zsh shell-ready wrapper'
|
||||
return `# ${header}
|
||||
# Why: capture the runtime wrapper dir before it is unset below. On WSL this
|
||||
# file is generated with a Windows path but sourced via /mnt/c, so the baked
|
||||
# literal is unusable there and ZDOTDIR must be restored from this value.
|
||||
_orca_wrapper_zdotdir_self="\${ZDOTDIR:-}"
|
||||
while [[ "\${_orca_wrapper_zdotdir_self:-}" == */ ]]; do
|
||||
_orca_wrapper_zdotdir_self="\${_orca_wrapper_zdotdir_self%/}"
|
||||
done
|
||||
_orca_spawn_orig_zdotdir="\${ORCA_ORIG_ZDOTDIR:-}"
|
||||
_orca_user_zdotdir="\${_orca_spawn_orig_zdotdir:-$HOME}"
|
||||
_orca_zshenv_source_dir="\${ORCA_ZSHENV_SOURCE_DIR:-$HOME}"
|
||||
|
|
@ -67,8 +74,14 @@ case "\${ORCA_ORIG_ZDOTDIR}" in
|
|||
""|*/shell-ready/zsh) export ORCA_ORIG_ZDOTDIR="$HOME" ;;
|
||||
esac
|
||||
|
||||
export ZDOTDIR=${quotePosixSingle(zshDir)}
|
||||
unset _orca_spawn_orig_zdotdir _orca_user_zdotdir _orca_zshenv_source_dir _orca_zshenv_path _orca_discovered_zdotdir
|
||||
# Why: use :- after user .zshenv — a pathological unset under set -u must not
|
||||
# abort the wrapper; empty falls through to the baked-literal branch.
|
||||
if [[ -n "\${_orca_wrapper_zdotdir_self:-}" && -f "\${_orca_wrapper_zdotdir_self:-}/.zshenv" ]]; then
|
||||
export ZDOTDIR="\${_orca_wrapper_zdotdir_self:-}"
|
||||
else
|
||||
export ZDOTDIR=${quotePosixSingle(zshDir)}
|
||||
fi
|
||||
unset _orca_spawn_orig_zdotdir _orca_user_zdotdir _orca_zshenv_source_dir _orca_zshenv_path _orca_discovered_zdotdir _orca_wrapper_zdotdir_self
|
||||
`
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue