Fix shell-ready zsh wrapper self loops (#1184)
This commit is contained in:
parent
d8d2c0f033
commit
ba0f263df1
|
|
@ -1,7 +1,7 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { tmpdir } from 'os'
|
||||
import { join } from 'path'
|
||||
import { existsSync, mkdtempSync, rmSync } from 'fs'
|
||||
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'fs'
|
||||
import type * as ShellReadyModule from './shell-ready'
|
||||
|
||||
async function importFreshShellReady(): Promise<typeof ShellReadyModule> {
|
||||
|
|
@ -90,6 +90,75 @@ describePosix('daemon shell-ready launch config', () => {
|
|||
}
|
||||
})
|
||||
|
||||
it('uses inherited ORCA_ORIG_ZDOTDIR when ZDOTDIR is an Orca wrapper dir', async () => {
|
||||
const previousZdotdir = process.env.ZDOTDIR
|
||||
const previousOrigZdotdir = process.env.ORCA_ORIG_ZDOTDIR
|
||||
const previousHome = process.env.HOME
|
||||
process.env.ZDOTDIR = '/some/other/orca/shell-ready/zsh'
|
||||
process.env.ORCA_ORIG_ZDOTDIR = '/Users/alice/.config/zsh'
|
||||
process.env.HOME = '/Users/alice'
|
||||
try {
|
||||
const { getShellReadyLaunchConfig } = await importFreshShellReady()
|
||||
const config = getShellReadyLaunchConfig('/bin/zsh')
|
||||
expect(config.env.ORCA_ORIG_ZDOTDIR).toBe('/Users/alice/.config/zsh')
|
||||
} finally {
|
||||
if (previousZdotdir === undefined) {
|
||||
delete process.env.ZDOTDIR
|
||||
} else {
|
||||
process.env.ZDOTDIR = previousZdotdir
|
||||
}
|
||||
if (previousOrigZdotdir === undefined) {
|
||||
delete process.env.ORCA_ORIG_ZDOTDIR
|
||||
} else {
|
||||
process.env.ORCA_ORIG_ZDOTDIR = previousOrigZdotdir
|
||||
}
|
||||
if (previousHome === undefined) {
|
||||
delete process.env.HOME
|
||||
} else {
|
||||
process.env.HOME = previousHome
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('falls back to HOME when inherited ORCA_ORIG_ZDOTDIR points at a wrapper dir', async () => {
|
||||
const previousZdotdir = process.env.ZDOTDIR
|
||||
const previousOrigZdotdir = process.env.ORCA_ORIG_ZDOTDIR
|
||||
const previousHome = process.env.HOME
|
||||
delete process.env.ZDOTDIR
|
||||
process.env.ORCA_ORIG_ZDOTDIR = '/some/other/orca/shell-ready/zsh'
|
||||
process.env.HOME = '/Users/alice'
|
||||
try {
|
||||
const { getShellReadyLaunchConfig } = await importFreshShellReady()
|
||||
const config = getShellReadyLaunchConfig('/bin/zsh')
|
||||
expect(config.env.ORCA_ORIG_ZDOTDIR).toBe('/Users/alice')
|
||||
} finally {
|
||||
if (previousZdotdir === undefined) {
|
||||
delete process.env.ZDOTDIR
|
||||
} else {
|
||||
process.env.ZDOTDIR = previousZdotdir
|
||||
}
|
||||
if (previousOrigZdotdir === undefined) {
|
||||
delete process.env.ORCA_ORIG_ZDOTDIR
|
||||
} else {
|
||||
process.env.ORCA_ORIG_ZDOTDIR = previousOrigZdotdir
|
||||
}
|
||||
if (previousHome === undefined) {
|
||||
delete process.env.HOME
|
||||
} else {
|
||||
process.env.HOME = previousHome
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('writes zsh wrappers that guard against ORCA_ORIG_ZDOTDIR self-loops', async () => {
|
||||
const { getShellReadyLaunchConfig } = await importFreshShellReady()
|
||||
|
||||
getShellReadyLaunchConfig('/bin/zsh')
|
||||
|
||||
const zshenv = readFileSync(join(userDataPath, 'shell-ready', 'zsh', '.zshenv'), 'utf8')
|
||||
expect(zshenv).toContain('*/shell-ready/zsh) export ORCA_ORIG_ZDOTDIR="$HOME" ;;')
|
||||
})
|
||||
|
||||
it('preserves a real inherited ZDOTDIR as ORCA_ORIG_ZDOTDIR', async () => {
|
||||
// Why: users who run a custom zsh dotfiles directory legitimately set
|
||||
// ZDOTDIR before launching Orca. We only want to reject the self-loop
|
||||
|
|
|
|||
|
|
@ -30,21 +30,29 @@ function getShellReadyWrapperRoot(): string {
|
|||
// (regardless of whether it came from this daemon's userData, a packaged
|
||||
// Orca, or a different dev build). Treat it as if ZDOTDIR were unset so the
|
||||
// caller falls back to HOME for the user's real config root.
|
||||
function resolveOriginalZdotdir(): string {
|
||||
const inherited = process.env.ZDOTDIR
|
||||
if (!inherited) {
|
||||
return process.env.HOME || ''
|
||||
function normalizeOriginalZdotdirCandidate(value: string | undefined): string | null {
|
||||
if (!value) {
|
||||
return null
|
||||
}
|
||||
// Why: tolerate trailing slashes — some shell startup scripts export
|
||||
// `ZDOTDIR="$dir/"`, and without normalization the suffix check would
|
||||
// miss the self-loop path and restore the recursion bug. Also collapses
|
||||
// a pathological `ZDOTDIR=/` to empty so we fall back to HOME rather than
|
||||
// sourcing `/.zshenv` (which is never the user's real config).
|
||||
const normalized = inherited.replace(/\/+$/, '')
|
||||
const normalized = value.replace(/\/+$/, '')
|
||||
if (!normalized || normalized.endsWith('/shell-ready/zsh')) {
|
||||
return process.env.HOME || ''
|
||||
return null
|
||||
}
|
||||
return inherited
|
||||
return value
|
||||
}
|
||||
|
||||
function resolveOriginalZdotdir(): string {
|
||||
return (
|
||||
normalizeOriginalZdotdirCandidate(process.env.ZDOTDIR) ||
|
||||
normalizeOriginalZdotdirCandidate(process.env.ORCA_ORIG_ZDOTDIR) ||
|
||||
process.env.HOME ||
|
||||
''
|
||||
)
|
||||
}
|
||||
|
||||
function getRequiredShellReadyWrapperPaths(root = getShellReadyWrapperRoot()): string[] {
|
||||
|
|
@ -76,15 +84,24 @@ function ensureShellReadyWrappers(): void {
|
|||
|
||||
const zshEnv = `# Orca daemon zsh shell-ready wrapper
|
||||
export ORCA_ORIG_ZDOTDIR="\${ORCA_ORIG_ZDOTDIR:-$HOME}"
|
||||
case "\${ORCA_ORIG_ZDOTDIR%/}" in
|
||||
*/shell-ready/zsh) export ORCA_ORIG_ZDOTDIR="$HOME" ;;
|
||||
esac
|
||||
[[ -f "$ORCA_ORIG_ZDOTDIR/.zshenv" ]] && source "$ORCA_ORIG_ZDOTDIR/.zshenv"
|
||||
export ZDOTDIR=${quotePosixSingle(zshDir)}
|
||||
`
|
||||
const zshProfile = `# Orca daemon zsh shell-ready wrapper
|
||||
_orca_home="\${ORCA_ORIG_ZDOTDIR:-$HOME}"
|
||||
case "\${_orca_home%/}" in
|
||||
*/shell-ready/zsh) _orca_home="$HOME" ;;
|
||||
esac
|
||||
[[ -f "$_orca_home/.zprofile" ]] && source "$_orca_home/.zprofile"
|
||||
`
|
||||
const zshRc = `# Orca daemon zsh shell-ready wrapper
|
||||
_orca_home="\${ORCA_ORIG_ZDOTDIR:-$HOME}"
|
||||
case "\${_orca_home%/}" in
|
||||
*/shell-ready/zsh) _orca_home="$HOME" ;;
|
||||
esac
|
||||
if [[ -o interactive && -f "$_orca_home/.zshrc" ]]; then
|
||||
source "$_orca_home/.zshrc"
|
||||
fi
|
||||
|
|
@ -99,6 +116,9 @@ __orca_restore_attribution_path() {
|
|||
`
|
||||
const zshLogin = `# Orca daemon zsh shell-ready wrapper
|
||||
_orca_home="\${ORCA_ORIG_ZDOTDIR:-$HOME}"
|
||||
case "\${_orca_home%/}" in
|
||||
*/shell-ready/zsh) _orca_home="$HOME" ;;
|
||||
esac
|
||||
if [[ -o interactive && -f "$_orca_home/.zlogin" ]]; then
|
||||
source "$_orca_home/.zlogin"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { tmpdir } from 'os'
|
||||
import { join } from 'path'
|
||||
import { mkdtempSync, rmSync } from 'fs'
|
||||
import { mkdtempSync, readFileSync, rmSync } from 'fs'
|
||||
import type * as pty from 'node-pty'
|
||||
import type * as LocalPtyShellReadyModule from './local-pty-shell-ready'
|
||||
import { writeStartupCommandWhenShellReady } from './local-pty-shell-ready'
|
||||
|
|
@ -165,6 +165,75 @@ describePosix('local PTY shell-ready launch config', () => {
|
|||
}
|
||||
})
|
||||
|
||||
it('uses inherited ORCA_ORIG_ZDOTDIR when ZDOTDIR is an Orca wrapper dir', async () => {
|
||||
const previousZdotdir = process.env.ZDOTDIR
|
||||
const previousOrigZdotdir = process.env.ORCA_ORIG_ZDOTDIR
|
||||
const previousHome = process.env.HOME
|
||||
process.env.ZDOTDIR = '/some/other/orca/shell-ready/zsh'
|
||||
process.env.ORCA_ORIG_ZDOTDIR = '/Users/alice/.config/zsh'
|
||||
process.env.HOME = '/Users/alice'
|
||||
try {
|
||||
const { getShellReadyLaunchConfig } = await importFreshLocalPtyShellReady()
|
||||
const config = getShellReadyLaunchConfig('/bin/zsh')
|
||||
expect(config.env.ORCA_ORIG_ZDOTDIR).toBe('/Users/alice/.config/zsh')
|
||||
} finally {
|
||||
if (previousZdotdir === undefined) {
|
||||
delete process.env.ZDOTDIR
|
||||
} else {
|
||||
process.env.ZDOTDIR = previousZdotdir
|
||||
}
|
||||
if (previousOrigZdotdir === undefined) {
|
||||
delete process.env.ORCA_ORIG_ZDOTDIR
|
||||
} else {
|
||||
process.env.ORCA_ORIG_ZDOTDIR = previousOrigZdotdir
|
||||
}
|
||||
if (previousHome === undefined) {
|
||||
delete process.env.HOME
|
||||
} else {
|
||||
process.env.HOME = previousHome
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('falls back to HOME when inherited ORCA_ORIG_ZDOTDIR points at a wrapper dir', async () => {
|
||||
const previousZdotdir = process.env.ZDOTDIR
|
||||
const previousOrigZdotdir = process.env.ORCA_ORIG_ZDOTDIR
|
||||
const previousHome = process.env.HOME
|
||||
delete process.env.ZDOTDIR
|
||||
process.env.ORCA_ORIG_ZDOTDIR = '/some/other/orca/shell-ready/zsh'
|
||||
process.env.HOME = '/Users/alice'
|
||||
try {
|
||||
const { getShellReadyLaunchConfig } = await importFreshLocalPtyShellReady()
|
||||
const config = getShellReadyLaunchConfig('/bin/zsh')
|
||||
expect(config.env.ORCA_ORIG_ZDOTDIR).toBe('/Users/alice')
|
||||
} finally {
|
||||
if (previousZdotdir === undefined) {
|
||||
delete process.env.ZDOTDIR
|
||||
} else {
|
||||
process.env.ZDOTDIR = previousZdotdir
|
||||
}
|
||||
if (previousOrigZdotdir === undefined) {
|
||||
delete process.env.ORCA_ORIG_ZDOTDIR
|
||||
} else {
|
||||
process.env.ORCA_ORIG_ZDOTDIR = previousOrigZdotdir
|
||||
}
|
||||
if (previousHome === undefined) {
|
||||
delete process.env.HOME
|
||||
} else {
|
||||
process.env.HOME = previousHome
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('writes zsh wrappers that guard against ORCA_ORIG_ZDOTDIR self-loops', async () => {
|
||||
const { getShellReadyLaunchConfig } = await importFreshLocalPtyShellReady()
|
||||
|
||||
getShellReadyLaunchConfig('/bin/zsh')
|
||||
|
||||
const zshenv = readFileSync(join(userDataPath, 'shell-ready', 'zsh', '.zshenv'), 'utf8')
|
||||
expect(zshenv).toContain('*/shell-ready/zsh) export ORCA_ORIG_ZDOTDIR="$HOME" ;;')
|
||||
})
|
||||
|
||||
it('preserves a real inherited ZDOTDIR as ORCA_ORIG_ZDOTDIR', async () => {
|
||||
const previousZdotdir = process.env.ZDOTDIR
|
||||
process.env.ZDOTDIR = '/Users/alice/.config/zsh'
|
||||
|
|
|
|||
|
|
@ -86,21 +86,29 @@ function getShellReadyWrapperRoot(): string {
|
|||
// (regardless of whether it came from this app's userData, a packaged Orca,
|
||||
// or a different dev build). Treat it as if ZDOTDIR were unset so the caller
|
||||
// falls back to HOME for the user's real config root.
|
||||
function resolveOriginalZdotdir(): string {
|
||||
const inherited = process.env.ZDOTDIR
|
||||
if (!inherited) {
|
||||
return process.env.HOME || ''
|
||||
function normalizeOriginalZdotdirCandidate(value: string | undefined): string | null {
|
||||
if (!value) {
|
||||
return null
|
||||
}
|
||||
// Why: tolerate trailing slashes — some shell startup scripts export
|
||||
// `ZDOTDIR="$dir/"`, and without normalization the suffix check would
|
||||
// miss the self-loop path and restore the recursion bug. Also collapses
|
||||
// a pathological `ZDOTDIR=/` to empty so we fall back to HOME rather than
|
||||
// sourcing `/.zshenv` (which is never the user's real config).
|
||||
const normalized = inherited.replace(/\/+$/, '')
|
||||
const normalized = value.replace(/\/+$/, '')
|
||||
if (!normalized || normalized.endsWith('/shell-ready/zsh')) {
|
||||
return process.env.HOME || ''
|
||||
return null
|
||||
}
|
||||
return inherited
|
||||
return value
|
||||
}
|
||||
|
||||
function resolveOriginalZdotdir(): string {
|
||||
return (
|
||||
normalizeOriginalZdotdirCandidate(process.env.ZDOTDIR) ||
|
||||
normalizeOriginalZdotdirCandidate(process.env.ORCA_ORIG_ZDOTDIR) ||
|
||||
process.env.HOME ||
|
||||
''
|
||||
)
|
||||
}
|
||||
|
||||
export function getBashShellReadyRcfileContent(): string {
|
||||
|
|
@ -156,15 +164,24 @@ function ensureShellReadyWrappers(): void {
|
|||
|
||||
const zshEnv = `# Orca zsh shell-ready wrapper
|
||||
export ORCA_ORIG_ZDOTDIR="\${ORCA_ORIG_ZDOTDIR:-$HOME}"
|
||||
case "\${ORCA_ORIG_ZDOTDIR%/}" in
|
||||
*/shell-ready/zsh) export ORCA_ORIG_ZDOTDIR="$HOME" ;;
|
||||
esac
|
||||
[[ -f "$ORCA_ORIG_ZDOTDIR/.zshenv" ]] && source "$ORCA_ORIG_ZDOTDIR/.zshenv"
|
||||
export ZDOTDIR=${quotePosixSingle(zshDir)}
|
||||
`
|
||||
const zshProfile = `# Orca zsh shell-ready wrapper
|
||||
_orca_home="\${ORCA_ORIG_ZDOTDIR:-$HOME}"
|
||||
case "\${_orca_home%/}" in
|
||||
*/shell-ready/zsh) _orca_home="$HOME" ;;
|
||||
esac
|
||||
[[ -f "$_orca_home/.zprofile" ]] && source "$_orca_home/.zprofile"
|
||||
`
|
||||
const zshRc = `# Orca zsh shell-ready wrapper
|
||||
_orca_home="\${ORCA_ORIG_ZDOTDIR:-$HOME}"
|
||||
case "\${_orca_home%/}" in
|
||||
*/shell-ready/zsh) _orca_home="$HOME" ;;
|
||||
esac
|
||||
if [[ -o interactive && -f "$_orca_home/.zshrc" ]]; then
|
||||
source "$_orca_home/.zshrc"
|
||||
fi
|
||||
|
|
@ -179,6 +196,9 @@ __orca_restore_attribution_path() {
|
|||
`
|
||||
const zshLogin = `# Orca zsh shell-ready wrapper
|
||||
_orca_home="\${ORCA_ORIG_ZDOTDIR:-$HOME}"
|
||||
case "\${_orca_home%/}" in
|
||||
*/shell-ready/zsh) _orca_home="$HOME" ;;
|
||||
esac
|
||||
if [[ -o interactive && -f "$_orca_home/.zlogin" ]]; then
|
||||
source "$_orca_home/.zlogin"
|
||||
fi
|
||||
|
|
|
|||
Loading…
Reference in New Issue