mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 18:17:36 +00:00
fix(wsl): start a bridge fish older than 2.7 without -C instead of failing the pane (#35579)
* fix(wsl): start a bridge fish older than 2.7 without -C instead of failing the pane fish before 2.7 rejects -C, so the claude function and cwd hook made a WSL fish pane fail to start on distros such as Ubuntu 16.04. Read the version first and start those plainly. The cwd-report test now decodes reports through the daemon's parser and checks fish 4's own reports. * fix(wsl): read a fish 2.x version from stderr fish 2.x prints --version to stderr, so the probe read an empty string and old fish started with -C and failed. Read both streams; the stub now reports one old version on stderr so the test pins this.
This commit is contained in:
@@ -5,22 +5,36 @@ import path from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { fishRequirementViolation, resolveFishBinary } from './fish-binary-requirement'
|
||||
import { getWslFishInitCommand } from './wsl-login-shell-command'
|
||||
import { toWindowsWslPath } from './wsl-paths'
|
||||
import { parseFileUriPath } from '../main/daemon/osc7-file-uri'
|
||||
|
||||
const FISH = resolveFishBinary(3)
|
||||
const FISH_PATH = FISH.available ? FISH.path : ''
|
||||
const itWithFish = FISH.available ? it : it.skip
|
||||
const PROMPT_MARK = 'ORCAFISH> '
|
||||
|
||||
// Orca's reports and the foreign one carry no host; fish 4's own (file://<host>/…) are left out.
|
||||
function hostlessReports(output: string): string[] {
|
||||
type Report = { hosted: boolean; folder: string }
|
||||
|
||||
// Every OSC 7 the run printed, read the way the daemon reads a WSL pane's (host ignored).
|
||||
function reports(output: string): Report[] {
|
||||
return output
|
||||
.split('\x1b]7;')
|
||||
.slice(1)
|
||||
.map((report) => report.slice(0, report.indexOf('\x07')))
|
||||
.filter((uri) => uri.startsWith('file:///'))
|
||||
.map((uri) => decodeURIComponent(uri.slice('file://'.length)))
|
||||
.map((uri) => ({
|
||||
hosted: !uri.startsWith('file:///'),
|
||||
folder: parseFileUriPath(uri, { wslDistro: 'Ubuntu' }) ?? 'unparsed'
|
||||
}))
|
||||
}
|
||||
|
||||
// Orca's reports and the foreign one carry no host; fish 4 also sends its own, with one.
|
||||
const hostless = (output: string): string[] =>
|
||||
reports(output)
|
||||
.filter((report) => !report.hosted)
|
||||
.map((report) => report.folder)
|
||||
|
||||
const unc = (folder: string): string => toWindowsWslPath(folder, 'Ubuntu')
|
||||
|
||||
describe('a WSL bridge fish pane reports its folder', () => {
|
||||
let home: string
|
||||
let deep: string
|
||||
@@ -103,7 +117,13 @@ describe('a WSL bridge fish pane reports its folder', () => {
|
||||
'at every prompt, after the user config, keeping their prompt and hook',
|
||||
async () => {
|
||||
const output = await runFish({ WSL_DISTRO_NAME: 'Ubuntu' })
|
||||
expect(hostlessReports(output)).toEqual([home, deep, deep, '/elsewhere', deep])
|
||||
expect(hostless(output)).toEqual([home, deep, deep, '/elsewhere', deep].map(unc))
|
||||
// fish 4's own hosted reports name the same folders, so the two never disagree.
|
||||
const hosted = reports(output).filter((report) => report.hosted)
|
||||
expect(hosted.length > 0).toBe(FISH.majorVersion >= 4)
|
||||
for (const report of hosted) {
|
||||
expect([unc(home), unc(deep)]).toContain(report.folder)
|
||||
}
|
||||
expect(output).toContain('USER_PROMPT_HOOK')
|
||||
},
|
||||
60_000
|
||||
@@ -112,7 +132,7 @@ describe('a WSL bridge fish pane reports its folder', () => {
|
||||
itWithFish(
|
||||
'only inside WSL',
|
||||
async () => {
|
||||
expect(hostlessReports(await runFish({}))).toEqual(['/elsewhere'])
|
||||
expect(hostless(await runFish({}))).toEqual([unc('/elsewhere')])
|
||||
},
|
||||
60_000
|
||||
)
|
||||
|
||||
@@ -76,8 +76,14 @@ export function buildWslInteractiveLoginShellCommand(): string {
|
||||
' fi',
|
||||
' ;;',
|
||||
// Why: a WSL fish pane gets no shell-ready wrapper or XDG handoff, so this is its only `claude`.
|
||||
// fish before 2.7 rejects -C and would fail the pane; it starts plain (no claude, no cwd hook).
|
||||
// fish 2.x prints --version to stderr, so both streams are read.
|
||||
' fish)',
|
||||
` exec "$_orca_wsl_shell" -l -C ${quotePosixShell(getWslFishInitCommand())}`,
|
||||
' _orca_fish_version=$("$_orca_wsl_shell" --version 2>&1)',
|
||||
' case "${_orca_fish_version##*version }" in',
|
||||
' [01].*|2.[0-6]|2.[0-6].*) ;;',
|
||||
` *) exec "$_orca_wsl_shell" -l -C ${quotePosixShell(getWslFishInitCommand())} ;;`,
|
||||
' esac',
|
||||
' ;;',
|
||||
' zsh)',
|
||||
' if [ -n "${_orca_shell_ready_root:-}" ] && [ -d "${_orca_shell_ready_root}/zsh" ]; then',
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterAll, describe, expect, it } from 'vitest'
|
||||
import { buildWslInteractiveLoginShellCommand } from './wsl-login-shell-command'
|
||||
|
||||
// A stub fish that reports a version and echoes the argv it was started with.
|
||||
const root = mkdtempSync(join(tmpdir(), 'orca-wsl-fish-version-'))
|
||||
const tools = join(root, 'tools')
|
||||
const fish = join(root, 'bin', 'fish')
|
||||
mkdirSync(tools)
|
||||
mkdirSync(join(root, 'bin'))
|
||||
writeFileSync(
|
||||
join(tools, 'getent'),
|
||||
`#!/bin/sh\nprintf '%s\\n' "user:x:1000:1000::/home/user:${fish}"\n`
|
||||
)
|
||||
writeFileSync(
|
||||
fish,
|
||||
[
|
||||
'#!/bin/sh',
|
||||
'if [ "$1" = --version ]; then',
|
||||
' [ -n "$ORCA_TEST_FISH_VERSION" ] || exit 1',
|
||||
// fish 2.x writes its version to stderr.
|
||||
' if [ -n "$ORCA_TEST_FISH_VERSION_ON_STDERR" ]; then',
|
||||
' printf "%s\\n" "$ORCA_TEST_FISH_VERSION" >&2; exit 0',
|
||||
' fi',
|
||||
' printf "%s\\n" "$ORCA_TEST_FISH_VERSION"; exit 0',
|
||||
'fi',
|
||||
'printf "%s\\n" "$@"',
|
||||
''
|
||||
].join('\n')
|
||||
)
|
||||
chmodSync(join(tools, 'getent'), 0o755)
|
||||
chmodSync(fish, 0o755)
|
||||
afterAll(() => rmSync(root, { recursive: true, force: true }))
|
||||
|
||||
function startFlags(version: string, onStderr = false): string[] {
|
||||
const argv = execFileSync('/bin/sh', ['-c', buildWslInteractiveLoginShellCommand()], {
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
PATH: `${tools}:/usr/bin:/bin`,
|
||||
ORCA_TEST_FISH_VERSION: version,
|
||||
...(onStderr ? { ORCA_TEST_FISH_VERSION_ON_STDERR: '1' } : {})
|
||||
}
|
||||
})
|
||||
return argv.split('\n').filter((arg) => arg === '-l' || arg === '-C')
|
||||
}
|
||||
|
||||
// fish before 2.7 rejects -C, which failed the whole bridge pane (Ubuntu 16.04, Debian 9).
|
||||
describe.skipIf(process.platform === 'win32')('a WSL bridge fish pane by fish version', () => {
|
||||
it.each(['fish, version 1.23.1', 'fish, version 2.2.0', 'fish, version 2.6.0'])(
|
||||
'%s starts plain, without -C',
|
||||
(version) => {
|
||||
expect(startFlags(version)).toEqual(['-l'])
|
||||
}
|
||||
)
|
||||
|
||||
it('reads a fish 2.x version printed to stderr and starts plain', () => {
|
||||
expect(startFlags('fish, version 2.3.1', true)).toEqual(['-l'])
|
||||
})
|
||||
|
||||
it.each([
|
||||
'fish, version 2.7.1',
|
||||
'fish, version 3.1.2',
|
||||
'fish, version 4.7.1',
|
||||
'fish, version 3.7.1-1234-gabcdef',
|
||||
// An unreadable version keeps today's launch.
|
||||
''
|
||||
])('%s starts with the -C init command', (version) => {
|
||||
expect(startFlags(version)).toEqual(['-l', '-C'])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user