fix(shell): make every wrapper file self-sufficient and retire the deleted marker vars from tests

- .zprofile/.zshrc/.zlogin each define __orca_resolve_user_config_dir. They
  called it on line 2 while only .zshenv defined it, so a wrapper dir written by
  two concurrently installed builds printed three "command not found" and
  skipped the user's entire zsh config. New live-shell test covers it.
- Retarget every remaining ORCA_SHELL_READY_MARKER/ORCA_SHELL_STARTUP_IDENTITY
  reference onto ORCA_SHELL_FEATURES, or delete it where the key is now dead.
- isOrcaMintedHistFile requires a leading '/', so a relative path of the same
  shape stays the user's.
- Drop an unused no-control-regex disable, and register the two real-zsh suites
  in the dedicated shell-contracts lane.
This commit is contained in:
Neil
2026-08-18 02:05:39 -07:00
parent a83d873eea
commit c7d5f2fb3e
23 changed files with 206 additions and 27 deletions
+4
View File
@@ -336,8 +336,10 @@ jobs:
src/main/providers/local-pty-shell-ready-zsh-zdotdir-normalization.test.ts \
src/main/providers/__tests__/shell-ready-framework-example.test.ts \
src/main/pty/omp-shell-wrapper.node-pty.test.ts \
src/main/shell-startup-feature-channel.test.ts \
src/main/terminal-history-fish-session.node-pty.test.ts \
src/main/zsh-scoped-histfile.live-shell.test.ts \
src/main/zsh-wrapper-version-mismatch.live-shell.test.ts \
src/renderer/src/components/terminal-pane/fish-color-scheme-child-stdin.node-pty.test.ts \
src/shared/startup-shell-portability.live-shell.test.ts \
src/shared/posix-command-path-lookup.test.ts
@@ -378,8 +380,10 @@ jobs:
--exclude=src/main/providers/local-pty-shell-ready-zsh-zdotdir-normalization.test.ts \
--exclude=src/main/providers/__tests__/shell-ready-framework-example.test.ts \
--exclude=src/main/pty/omp-shell-wrapper.node-pty.test.ts \
--exclude=src/main/shell-startup-feature-channel.test.ts \
--exclude=src/main/terminal-history-fish-session.node-pty.test.ts \
--exclude=src/main/zsh-scoped-histfile.live-shell.test.ts \
--exclude=src/main/zsh-wrapper-version-mismatch.live-shell.test.ts \
--exclude=src/renderer/src/components/terminal-pane/fish-color-scheme-child-stdin.node-pty.test.ts \
--exclude=src/shared/startup-shell-portability.live-shell.test.ts \
--exclude=src/shared/posix-command-path-lookup.test.ts \
@@ -15,7 +15,9 @@ const shellContractFiles = [
'src/main/providers/local-pty-shell-ready-zsh-zdotdir-discovery.test.ts',
'src/main/providers/local-pty-shell-ready-zsh-zdotdir-normalization.test.ts',
'src/main/providers/__tests__/shell-ready-framework-example.test.ts',
'src/main/shell-startup-feature-channel.test.ts',
'src/main/zsh-scoped-histfile.live-shell.test.ts',
'src/main/zsh-wrapper-version-mismatch.live-shell.test.ts',
'src/shared/posix-command-path-lookup.test.ts'
]
const patchedNodePtyContractFiles = [
@@ -29,7 +29,7 @@ function runInteractiveBash(
{
input,
encoding: 'utf8',
env: { ...process.env, HOME: tempHome, ORCA_SHELL_READY_MARKER: '1', TERM: 'xterm' },
env: { ...process.env, HOME: tempHome, ORCA_SHELL_FEATURES: 'ready', TERM: 'xterm' },
timeout: 5000
}
)
+7 -7
View File
@@ -233,14 +233,14 @@ describe('createPtySubprocess', () => {
resolveUnixShellPathMock.mockReturnValue('/bin/sh')
const platform = Object.getOwnPropertyDescriptor(process, 'platform')
const previousShell = process.env.SHELL
const previousMarker = process.env.ORCA_SHELL_READY_MARKER
const previousFeatures = process.env.ORCA_SHELL_FEATURES
const previousZdotdir = process.env.ZDOTDIR
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
Object.defineProperty(process, 'platform', { configurable: true, value: 'linux' })
delete process.env.SHELL
// Why: the test runner itself can execute inside an Orca-wrapped shell
// whose exported wrapper vars would leak through the process.env spread.
delete process.env.ORCA_SHELL_READY_MARKER
delete process.env.ORCA_SHELL_FEATURES
delete process.env.ZDOTDIR
try {
@@ -257,9 +257,9 @@ describe('createPtySubprocess', () => {
expect(shellPath).toBe('/bin/sh')
expect(shellArgs).toEqual(['-l'])
// A launch config derived from the missing preferred zsh would inject
// ZDOTDIR and ORCA_SHELL_READY_MARKER; /bin/sh must spawn without them.
// ZDOTDIR and ORCA_SHELL_FEATURES; /bin/sh must spawn without them.
expect(spawnOptions.env.ZDOTDIR).toBeUndefined()
expect(spawnOptions.env.ORCA_SHELL_READY_MARKER).toBeUndefined()
expect(spawnOptions.env.ORCA_SHELL_FEATURES).toBeUndefined()
expect(spawnOptions.env.SHELL).toBe('/bin/sh')
} finally {
warn.mockRestore()
@@ -271,10 +271,10 @@ describe('createPtySubprocess', () => {
} else {
process.env.SHELL = previousShell
}
if (previousMarker === undefined) {
delete process.env.ORCA_SHELL_READY_MARKER
if (previousFeatures === undefined) {
delete process.env.ORCA_SHELL_FEATURES
} else {
process.env.ORCA_SHELL_READY_MARKER = previousMarker
process.env.ORCA_SHELL_FEATURES = previousFeatures
}
if (previousZdotdir === undefined) {
delete process.env.ZDOTDIR
@@ -412,7 +412,8 @@ describe('registerPtyHandlers', () => {
cwd: '/tmp',
env: expect.objectContaining({
ORCA_OPENCODE_CONFIG_DIR: '/tmp/orca-opencode-config',
ORCA_SHELL_READY_MARKER: '0',
// No `ready`: the fallback shell carries an overlay, not a startup command.
ORCA_SHELL_FEATURES: 'overlay,history,markers',
ZDOTDIR: '/tmp/orca-user-data/shell-ready/zsh'
})
})
@@ -39,7 +39,7 @@ EOF
- Lines before the marker → setup commands
- Lines after the marker → run command to test
3. **Gets Orca's wrapper config** by calling `getShellReadyLaunchConfig()`
3. **Gets Orca's wrapper config** by calling `getShellLaunchConfig()` with the features `selectShellStartupFeatures()` picks for a startup-command launch
4. **Executes setup** (if present) with bash in temp HOME, using wrapper env
@@ -51,7 +51,7 @@ function runInteractiveBashRcfile(
env: {
...process.env,
HOME: tempDir,
ORCA_SHELL_READY_MARKER: '1',
ORCA_SHELL_FEATURES: 'ready',
TERM: process.env.TERM || 'xterm'
},
timeout: 5000
@@ -72,8 +72,8 @@ function launchCodexThroughRcfile(fixture: Fixture, preflightValue: string): voi
PATH: ['/usr/bin', '/bin', '/usr/sbin', '/sbin'].join(delimiter),
TERM: 'dumb',
SHELL: '/bin/bash',
ORCA_SHELL_READY_MARKER: '0',
ORCA_SHELL_STARTUP_IDENTITY: '0',
// Why no ORCA_SHELL_FEATURES: absent means no features, so the rcfile
// emits neither the identity nor the readiness marker into stdout.
ORCA_CODEX_HOME: join(fixture.root, 'codex-home'),
ORCA_CODEX_LAUNCH_PREFLIGHT: preflightValue
}
@@ -371,8 +371,7 @@ describePosix('history-only pane in a real zsh', () => {
env: { PATH: '/usr/bin:/bin', HOME: home }
})
// eslint-disable-next-line no-control-regex
expect(wrapped).not.toMatch(/]133;/)
expect(wrapped).not.toContain('\x1b]133;')
expect(wrapped).toContain('PRECMD=\n')
expect(wrapped).toContain('LINEINIT=none')
// Startup files run in the same order with the same hooks, so the pane is
+15 -2
View File
@@ -45,6 +45,12 @@ export const SHELL_STARTUP_IDENTITY_MARKER_BLOCK = `__orca_has_feature identity
* its own. A stamped marker file (or Orca's own wrapper path shape, for
* wrappers written by older builds) is that proof. Guessing at other
* terminals' wrapper dirs by name never can be, so this never tries.
*
* Why every generated file redefines it instead of relying on .zshenv: one
* wrapper dir can be rewritten by two concurrently installed builds, so a shell
* can read one build's .zshenv and another's .zshrc. A .zshrc that called a
* function only the newer .zshenv defines printed `command not found` AND left
* $REPLY empty, which skipped sourcing the user's own startup file entirely.
*/
export const ZSH_USER_CONFIG_DIR_RESOLVER_BLOCK = `__orca_resolve_user_config_dir() {
REPLY="\${1:-}"
@@ -52,8 +58,15 @@ export const ZSH_USER_CONFIG_DIR_RESOLVER_BLOCK = `__orca_resolve_user_config_di
if [[ -z "$REPLY" || -f "$REPLY/${ZSH_WRAPPER_DIR_MARKER_FILE}" || "$REPLY" == */shell-ready/zsh ]]; then
REPLY="$HOME"
fi
}
# Why stricter for an inherited value: Orca can be launched from a terminal that
}`
/**
* The stricter resolver .zshenv uses for a ZDOTDIR this shell INHERITED.
*
* Why only .zshenv needs it: nothing after .zshenv re-reads an inherited value —
* the later files resolve ORCA_ORIG_ZDOTDIR, which .zshenv already vetted.
*/
export const ZSH_INHERITED_CONFIG_DIR_RESOLVER_BLOCK = `# Why stricter for an inherited value: Orca can be launched from a terminal that
# already pointed ZDOTDIR at its own wrapper dir, and a directory holding no zsh
# startup file at all is not the user's config root whoever wrote it.
__orca_resolve_inherited_config_dir() {
@@ -1,4 +1,11 @@
# Orca daemon zsh shell-ready wrapper
__orca_resolve_user_config_dir() {
REPLY="${1:-}"
while [[ "$REPLY" == */ ]]; do REPLY="${REPLY%/}"; done
if [[ -z "$REPLY" || -f "$REPLY/.orca-shell-wrapper" || "$REPLY" == */shell-ready/zsh ]]; then
REPLY="$HOME"
fi
}
__orca_resolve_user_config_dir "${ORCA_ORIG_ZDOTDIR:-$HOME}"
_orca_home="$REPLY"
if [[ -o interactive && -f "$_orca_home/.zlogin" ]]; then
@@ -1,4 +1,11 @@
# Orca daemon zsh shell-ready wrapper
__orca_resolve_user_config_dir() {
REPLY="${1:-}"
while [[ "$REPLY" == */ ]]; do REPLY="${REPLY%/}"; done
if [[ -z "$REPLY" || -f "$REPLY/.orca-shell-wrapper" || "$REPLY" == */shell-ready/zsh ]]; then
REPLY="$HOME"
fi
}
__orca_resolve_user_config_dir "${ORCA_ORIG_ZDOTDIR:-$HOME}"
_orca_home="$REPLY"
if [[ -f "$_orca_home/.zprofile" ]]; then
@@ -1,4 +1,11 @@
# Orca daemon zsh shell-ready wrapper
__orca_resolve_user_config_dir() {
REPLY="${1:-}"
while [[ "$REPLY" == */ ]]; do REPLY="${REPLY%/}"; done
if [[ -z "$REPLY" || -f "$REPLY/.orca-shell-wrapper" || "$REPLY" == */shell-ready/zsh ]]; then
REPLY="$HOME"
fi
}
__orca_resolve_user_config_dir "${ORCA_ORIG_ZDOTDIR:-$HOME}"
_orca_home="$REPLY"
if [[ "$_orca_home" != "$ZDOTDIR" && -o interactive && -f "$_orca_home/.zshrc" ]]; then
@@ -1,4 +1,11 @@
# Orca zsh shell-ready wrapper
__orca_resolve_user_config_dir() {
REPLY="${1:-}"
while [[ "$REPLY" == */ ]]; do REPLY="${REPLY%/}"; done
if [[ -z "$REPLY" || -f "$REPLY/.orca-shell-wrapper" || "$REPLY" == */shell-ready/zsh ]]; then
REPLY="$HOME"
fi
}
__orca_resolve_user_config_dir "${ORCA_ORIG_ZDOTDIR:-$HOME}"
_orca_home="$REPLY"
if [[ -o interactive && -f "$_orca_home/.zlogin" ]]; then
@@ -1,4 +1,11 @@
# Orca zsh shell-ready wrapper
__orca_resolve_user_config_dir() {
REPLY="${1:-}"
while [[ "$REPLY" == */ ]]; do REPLY="${REPLY%/}"; done
if [[ -z "$REPLY" || -f "$REPLY/.orca-shell-wrapper" || "$REPLY" == */shell-ready/zsh ]]; then
REPLY="$HOME"
fi
}
__orca_resolve_user_config_dir "${ORCA_ORIG_ZDOTDIR:-$HOME}"
_orca_home="$REPLY"
if [[ -f "$_orca_home/.zprofile" ]]; then
@@ -1,4 +1,11 @@
# Orca zsh shell-ready wrapper
__orca_resolve_user_config_dir() {
REPLY="${1:-}"
while [[ "$REPLY" == */ ]]; do REPLY="${REPLY%/}"; done
if [[ -z "$REPLY" || -f "$REPLY/.orca-shell-wrapper" || "$REPLY" == */shell-ready/zsh ]]; then
REPLY="$HOME"
fi
}
__orca_resolve_user_config_dir "${ORCA_ORIG_ZDOTDIR:-$HOME}"
_orca_home="$REPLY"
if [[ "$_orca_home" != "$ZDOTDIR" && -o interactive && -f "$_orca_home/.zshrc" ]]; then
@@ -1,4 +1,11 @@
# Orca relay zsh overlay wrapper
__orca_resolve_user_config_dir() {
REPLY="${1:-}"
while [[ "$REPLY" == */ ]]; do REPLY="${REPLY%/}"; done
if [[ -z "$REPLY" || -f "$REPLY/.orca-shell-wrapper" || "$REPLY" == */shell-ready/zsh ]]; then
REPLY="$HOME"
fi
}
__orca_resolve_user_config_dir "${ORCA_USER_ZDOTDIR:-${ORCA_ORIG_ZDOTDIR:-$HOME}}"
_orca_home="$REPLY"
if [[ -o interactive && -f "$_orca_home/.zlogin" ]]; then
@@ -1,4 +1,11 @@
# Orca relay zsh overlay wrapper
__orca_resolve_user_config_dir() {
REPLY="${1:-}"
while [[ "$REPLY" == */ ]]; do REPLY="${REPLY%/}"; done
if [[ -z "$REPLY" || -f "$REPLY/.orca-shell-wrapper" || "$REPLY" == */shell-ready/zsh ]]; then
REPLY="$HOME"
fi
}
__orca_resolve_user_config_dir "${ORCA_USER_ZDOTDIR:-${ORCA_ORIG_ZDOTDIR:-$HOME}}"
_orca_home="$REPLY"
if [[ -f "$_orca_home/.zprofile" ]]; then
@@ -1,4 +1,11 @@
# Orca relay zsh overlay wrapper
__orca_resolve_user_config_dir() {
REPLY="${1:-}"
while [[ "$REPLY" == */ ]]; do REPLY="${REPLY%/}"; done
if [[ -z "$REPLY" || -f "$REPLY/.orca-shell-wrapper" || "$REPLY" == */shell-ready/zsh ]]; then
REPLY="$HOME"
fi
}
__orca_resolve_user_config_dir "${ORCA_USER_ZDOTDIR:-${ORCA_ORIG_ZDOTDIR:-$HOME}}"
_orca_home="$REPLY"
if [[ -o interactive && -f "$_orca_home/.zshrc" ]]; then
+11 -3
View File
@@ -402,13 +402,21 @@ describe('terminal-history', () => {
expect(env.HISTFILE).not.toContain(OTHER_WORKTREE_HASH)
})
it('preserves a HISTFILE the user set themselves', () => {
it.each([
['an ordinary path', ['', 'home', 'me', '.zsh_history'].join(sep)],
// Orca only ever mints absolute paths, so the same shape relative to the
// user's cwd is theirs.
[
'a relative path of Orca’s shape',
['terminal-history', OTHER_WORKTREE_HASH, 'zsh_history'].join(sep)
]
])('preserves %s the user set as HISTFILE', (_kind, histFile) => {
// Only a path Orca minted is droppable; everything else is the user's.
const env: Record<string, string> = { HISTFILE: ['', 'home', 'me', '.zsh_history'].join(sep) }
const env: Record<string, string> = { HISTFILE: histFile }
const result = injectHistoryEnv(env, 'repo-1::/path/wt', '/bin/zsh', '/path/wt')
expect(env.HISTFILE).toBe(['', 'home', 'me', '.zsh_history'].join(sep))
expect(env.HISTFILE).toBe(histFile)
expect(result.histFile).toBeNull()
})
+14 -1
View File
@@ -15,14 +15,27 @@
* the desktop (Electron userData), the daemon subprocess (no Electron), and the
* relay on a remote host. Desktop and relay drop each other's shapes on purpose
* — neither owns the other's worktree ids.
*
* Why the shape is enough without an Orca-specific token: two of the three
* shapes carry one already (`.orca-remote`, `terminal-history-wsl`), and the
* third needs an absolute path whose LAST TWO segments are a 16-char lowercase
* hex directory directly under `terminal-history`, holding a file named exactly
* `zsh_history`/`bash_history`. That is a machine-minted layout, not one a
* person types. The blast radius if it were ever hit is also bounded: the value
* is dropped from ONE spawn's env, so the pane gets Orca's own worktree history
* (isolation on) or the shell's default (isolation off). Nothing on disk is
* read, written, moved, or deleted.
*/
/** 16 hex chars: `hashWorktreeId`. */
const WORKTREE_HASH = '[0-9a-f]{16}'
const HISTORY_FILE = '(?:zsh|bash)_history'
// Why a leading `/` rather than `(?:^|/)`: every minted value is absolute (or a
// Windows path normalized to forward slashes), so a relative path of the same
// shape is the user's, not Orca's.
const ORCA_MINTED_HISTFILE = new RegExp(
'(?:^|/)(?:' +
'/(?:' +
// Desktop: <userData>/terminal-history/<hash>/<file>
`terminal-history/${WORKTREE_HASH}/${HISTORY_FILE}` +
'|' +
+15 -6
View File
@@ -7,6 +7,9 @@
* disagree on is a field on ZshStartupWrapperSpec, so the disagreements are
* visible in one place instead of spread across three template literals.
*
* Every generated file redefines the helpers it calls, so the epilogue is the
* only thing one file needs another to have defined — see EPILOGUE_CALL.
*
* All order-sensitive Orca work lives in ONE `__orca_shell_epilogue` defined in
* .zshenv and invoked exactly once — from .zshrc for a non-login shell, from
* .zlogin for a login shell. Each feature inside it is an independent guard on
@@ -26,6 +29,7 @@ import {
SHELL_STARTUP_IDENTITY_MARKER_BLOCK,
ZSH_FEATURE_CHANNEL_BLOCK,
ZSH_HISTFILE_RESTORE_BLOCK,
ZSH_INHERITED_CONFIG_DIR_RESOLVER_BLOCK,
ZSH_USER_CONFIG_DIR_RESOLVER_BLOCK
} from './shell-templates'
@@ -180,6 +184,7 @@ function buildZshenv(spec: ZshStartupWrapperSpec): string {
ZSH_FEATURE_CHANNEL_BLOCK,
SHELL_STARTUP_IDENTITY_MARKER_BLOCK,
ZSH_USER_CONFIG_DIR_RESOLVER_BLOCK,
ZSH_INHERITED_CONFIG_DIR_RESOLVER_BLOCK,
spec.zshenvStrategy === 'discover-user-zdotdir'
? getZshEnvDiscoveryBody(spec.zshDir)
: getZshOverlayEnvBody(spec.zshDir),
@@ -188,12 +193,13 @@ function buildZshenv(spec: ZshStartupWrapperSpec): string {
}
/**
* Why guarded: the generated files are inter-dependent (this call, the function
* in .zshenv), and one wrapper dir can be shared by two concurrently installed
* Orca builds. Pairing this file with an older .zshenv then loses the features
* either way; degrade silently instead of printing "command not found" into the
* user's pane. Braced subscript because sh/ksh emulation (KSH_ARRAYS) rejects
* the unbraced `$+functions[...]` form outright.
* Why guarded: one wrapper dir can be shared by two concurrently installed Orca
* builds, so a shell can read one build's .zshenv and another's .zshrc/.zlogin.
* The epilogue is the only cross-file dependency left — every other function
* these files call is redefined at the top of each of them — so an older .zshenv
* costs this shell the epilogue's features and nothing else, without printing
* "command not found" into the user's pane. Braced subscript because sh/ksh
* emulation (KSH_ARRAYS) rejects the unbraced `$+functions[...]` form outright.
*/
const EPILOGUE_CALL = '(( ${+functions[__orca_shell_epilogue]} )) && __orca_shell_epilogue'
@@ -217,6 +223,7 @@ export function buildZshStartupWrapperFiles(spec: ZshStartupWrapperSpec): ZshSta
zshenv: buildZshenv(spec),
zprofile: `${joinBlocks([
`# ${spec.headerLabel}`,
ZSH_USER_CONFIG_DIR_RESOLVER_BLOCK,
getZshStartupFileSourceBlock({
fileName: '.zprofile',
homeExpression: spec.homeExpression
@@ -224,6 +231,7 @@ export function buildZshStartupWrapperFiles(spec: ZshStartupWrapperSpec): ZshSta
])}\n`,
zshrc: `${joinBlocks([
`# ${spec.headerLabel}`,
ZSH_USER_CONFIG_DIR_RESOLVER_BLOCK,
getZshStartupFileSourceBlock({
fileName: '.zshrc',
homeExpression: spec.homeExpression,
@@ -234,6 +242,7 @@ export function buildZshStartupWrapperFiles(spec: ZshStartupWrapperSpec): ZshSta
])}\n`,
zlogin: `${joinBlocks([
`# ${spec.headerLabel}`,
ZSH_USER_CONFIG_DIR_RESOLVER_BLOCK,
getZshStartupFileSourceBlock({
fileName: '.zlogin',
homeExpression: spec.homeExpression,
@@ -0,0 +1,67 @@
/**
* Real-zsh proof that a wrapper dir written by two different Orca builds still
* loads the user's own zsh config.
*
* One wrapper dir (`<userData>/shell-ready/zsh`, or `~/.orca-relay/shell-ready/
* zsh` for a remote host) is shared by every concurrently installed build, and
* each rewrites it on spawn. A shell can therefore read one build's `.zshenv`
* and another's `.zprofile`/`.zshrc`/`.zlogin`. Only a real zsh shows the cost:
* a helper the newer files call but the older `.zshenv` never defined both
* prints `command not found` into the pane AND leaves `$REPLY` empty, which
* silently skips sourcing the user's own startup files.
*/
import { spawnSync } from 'node:child_process'
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
import { ensureShellReadyWrappersAt } from './providers/local-pty-shell-ready-wrapper-generation'
const hasZsh = process.platform !== 'win32' && spawnSync('zsh', ['--version']).status === 0
const ZSH_PATH = hasZsh
? (spawnSync('sh', ['-c', 'command -v zsh'], { encoding: 'utf8' }).stdout || '').trim()
: ''
const itWithZsh = hasZsh ? it : it.skip
/**
* A `.zshenv` from a build that predates everything the other three files now
* call: it exports the wrapper ZDOTDIR and nothing else.
*/
function olderBuildZshenv(zshDir: string): string {
return `# Orca zsh shell-ready wrapper
export ORCA_ORIG_ZDOTDIR="$HOME"
export ZDOTDIR=${JSON.stringify(zshDir)}
`
}
describe.skipIf(process.platform === 'win32')('zsh wrapper dir written by mixed builds', () => {
itWithZsh('still sources the user config when the .zshenv is from an older build', () => {
const root = mkdtempSync(join(tmpdir(), 'orca-wrapper-mismatch-'))
const home = mkdtempSync(join(tmpdir(), 'orca-wrapper-mismatch-home-'))
try {
expect(ensureShellReadyWrappersAt(root)).toBe(true)
const zshDir = join(root, 'zsh')
writeFileSync(join(zshDir, '.zshenv'), olderBuildZshenv(zshDir))
for (const file of ['.zprofile', '.zshrc', '.zlogin']) {
writeFileSync(join(home, file), `echo "RAN ${file}"\n`)
}
const result = spawnSync(ZSH_PATH, ['-l', '-i', '-c', 'exit 0'], {
encoding: 'utf8',
timeout: 20_000,
env: { PATH: '/usr/bin:/bin', HOME: home, ZDOTDIR: zshDir }
})
// Why both streams: zsh prints `command not found` on stderr and the
// fixture files echo on stdout, and this asserts about each.
const output = `${result.stdout}${result.stderr}`
expect(output).not.toContain('command not found')
expect(output).toContain('RAN .zprofile')
expect(output).toContain('RAN .zshrc')
expect(output).toContain('RAN .zlogin')
} finally {
rmSync(root, { recursive: true, force: true })
rmSync(home, { recursive: true, force: true })
}
})
})