Files
orca/src/main/cli/wsl-cli-installer.test.ts
T

844 lines
32 KiB
TypeScript

import type { CliInstallStatus } from '../../shared/cli-install-types'
import { execFileSync } from 'node:child_process'
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const execFileMock = vi.hoisted(() => vi.fn())
vi.mock('node:child_process', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
execFile: execFileMock
}))
import { WslCliInstaller, _internals } from './wsl-cli-installer'
import { reconcileManagedWslCliRegistrations } from './wsl-cli-registration-reconciliation'
function makeHostStatus(
launcherPath = 'C:\\Users\\me\\AppData\\Local\\Programs\\Orca\\resources\\bin\\orca.exe'
) {
return {
platform: 'win32',
commandName: 'orca',
commandPath: launcherPath,
pathDirectory: 'C:\\Users\\me\\AppData\\Local\\Programs\\Orca\\resources\\bin',
pathConfigured: true,
launcherPath,
installMethod: 'wrapper',
supported: true,
state: 'installed',
currentTarget: launcherPath,
unsupportedReason: null,
detail: null
} satisfies CliInstallStatus
}
// Frozen from v1.4.138-rc.2: the upgrade regression only reproduces when the
// persisted managed script still names the pre-native Windows batch launcher.
const PRE_RC4_MANAGED_WSL_LAUNCHER = `#!/usr/bin/env bash
set -euo pipefail
# Orca managed WSL CLI launcher
# ORCA_WIN_LAUNCHER_B64=QzpcUHJvZ3JhbSBGaWxlc1xPcmNhXHJlc291cmNlc1xiaW5cb3JjYS5jbWQ=
ORCA_WIN_LAUNCHER='C:\\Program Files\\Orca\\resources\\bin\\orca.cmd'
ORCA_BRIDGE_PS1='/home/alice/.local/share/orca/orca-wsl-bridge.ps1'
if command -v powershell.exe >/dev/null 2>&1; then
ORCA_POWERSHELL=powershell.exe
elif [ -x /mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe ]; then
ORCA_POWERSHELL=/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe
else
echo "Orca WSL CLI requires Windows interop and could not find powershell.exe." >&2
exit 1
fi
ORCA_BRIDGE_PS1_WIN=$(wslpath -w "$ORCA_BRIDGE_PS1")
exec "$ORCA_POWERSHELL" -NoProfile -ExecutionPolicy Bypass -File "$ORCA_BRIDGE_PS1_WIN" "$ORCA_WIN_LAUNCHER" "$@"
`
function createWslRunner(
initialFile: string | null = null,
pathIncludesLocalBin = true,
options: {
initialBridge?: string | null
initialLegacyFile?: string | null
failInstall?: boolean
interopReady?: boolean
} = {}
) {
const commandPath = '/home/alice/.local/bin/orca-ide'
const legacyCommandPath = '/home/alice/.local/bin/orca'
const bridgePath = '/home/alice/.local/share/orca/orca-wsl-bridge.ps1'
const files = new Map<string, string>()
if (initialFile !== null) {
files.set(commandPath, initialFile)
}
if (
options.initialBridge !== null &&
(initialFile !== null || options.initialBridge !== undefined)
) {
files.set(bridgePath, options.initialBridge ?? _internals.buildWslBridgeScript())
}
if (options.initialLegacyFile) {
files.set(legacyCommandPath, options.initialLegacyFile)
}
const calls: string[] = []
const runner = vi.fn(async (_distro: string, command: string) => {
calls.push(command)
if (command.includes('printf %s "$HOME"')) {
return '/home/alice'
}
if (command.includes('case ":$PATH:"')) {
return pathIncludesLocalBin ? 'yes' : 'no'
}
if (command.includes('cat > "$command_tmp"')) {
if (options.failInstall) {
throw new Error('simulated replacement failure')
}
if (
files.has(bridgePath) &&
!files.get(bridgePath)?.includes('# Orca managed WSL CLI PowerShell bridge')
) {
throw new Error('__ORCA_CONFLICT__')
}
const launcher =
command.match(/cat > "\$command_tmp" <<'ORCA_WSL_CLI'\n([\s\S]*)\nORCA_WSL_CLI/)?.[1] ?? ''
const bridge =
command.match(
/cat > "\$bridge_tmp" <<'ORCA_WSL_BRIDGE'\n([\s\S]*)\nORCA_WSL_BRIDGE/
)?.[1] ?? ''
files.set(commandPath, launcher)
files.set(bridgePath, bridge)
if (files.get(legacyCommandPath)?.includes('# Orca managed WSL CLI launcher')) {
files.delete(legacyCommandPath)
}
return ''
}
if (command.includes('command -v powershell.exe')) {
return options.interopReady === false ? 'no' : 'yes'
}
if (command.includes('rm -f')) {
if (command.includes(`rm -f '${commandPath}'`)) {
if (
files.has(bridgePath) &&
!files.get(bridgePath)?.includes('# Orca managed WSL CLI PowerShell bridge')
) {
throw new Error('__ORCA_CONFLICT__')
}
files.delete(commandPath)
files.delete(bridgePath)
}
if (
command.includes(legacyCommandPath) &&
files.get(legacyCommandPath)?.includes('# Orca managed WSL CLI launcher')
) {
files.delete(legacyCommandPath)
}
return ''
}
if (command.includes('cat ')) {
if (command.includes(commandPath)) {
return files.get(commandPath) ?? '__ORCA_MISSING__'
}
if (command.includes(bridgePath)) {
return files.get(bridgePath) ?? '__ORCA_MISSING__'
}
if (command.includes(legacyCommandPath)) {
return files.get(legacyCommandPath) ?? '__ORCA_MISSING__'
}
}
throw new Error(`Unexpected WSL command: ${command}`)
})
return {
runner,
calls,
getBridge: () => files.get(bridgePath) ?? null,
getFile: () => files.get(commandPath) ?? null,
getLegacyFile: () => files.get(legacyCommandPath) ?? null
}
}
describe('WslCliInstaller', () => {
beforeEach(() => {
execFileMock.mockReset()
})
afterEach(() => {
vi.useRealTimers()
})
it('installs a WSL launcher that forwards to the Windows Orca launcher', async () => {
const wsl = createWslRunner()
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus() },
wslRunner: wsl.runner
})
await expect(installer.getStatus()).resolves.toMatchObject({
state: 'not_installed',
commandPath: '/home/alice/.local/bin/orca-ide'
})
const installed = await installer.install()
expect(installed).toMatchObject({
state: 'installed',
pathConfigured: true,
launcherPath: 'C:\\Users\\me\\AppData\\Local\\Programs\\Orca\\resources\\bin\\orca.exe'
})
expect(wsl.getFile()).toBe(
_internals.buildWslLauncher(
'C:\\Users\\me\\AppData\\Local\\Programs\\Orca\\resources\\bin\\orca.exe',
'/home/alice/.local/share/orca/orca-wsl-bridge.ps1'
)
)
expect(wsl.getBridge()).toBe(_internals.buildWslBridgeScript())
const installCommand = wsl.calls.find((command) => command.includes('cat > "$command_tmp"'))
expect(installCommand).toBeDefined()
expect(installCommand).toContain("legacy_command_path='/home/alice/.local/bin/orca'")
expect(installCommand).toContain('rm -f "$legacy_command_path"')
// Why: the new bridge accepts the old launcher's positional arguments, so
// publishing it first keeps interrupted upgrades usable.
const bridgePublishIndex = installCommand?.indexOf('mv -f "$bridge_tmp"') ?? -1
const launcherPublishIndex = installCommand?.indexOf('mv -f "$command_tmp"') ?? -1
expect(bridgePublishIndex).toBeGreaterThan(-1)
expect(bridgePublishIndex).toBeLessThan(launcherPublishIndex)
expect(installCommand).toContain('[ ! -L "$legacy_command_path" ]')
})
it('continues checking WSL when the host launcher exists but host PATH is unknown', async () => {
const wsl = createWslRunner()
const hostStatus = {
...makeHostStatus(),
pathConfigured: null,
detail: 'Orca could not read the Windows user PATH registry value.'
} satisfies CliInstallStatus
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => hostStatus },
wslRunner: wsl.runner
})
await expect(installer.getStatus()).resolves.toMatchObject({
supported: true,
state: 'not_installed',
commandPath: '/home/alice/.local/bin/orca-ide'
})
})
it('derives the shared WSL bridge path for current and legacy command names', () => {
expect(_internals.getBridgePathFromCommandPath('/home/alice/.local/bin/orca-ide')).toBe(
'/home/alice/.local/share/orca/orca-wsl-bridge.ps1'
)
expect(_internals.getBridgePathFromCommandPath('/home/alice/.local/bin/orca')).toBe(
'/home/alice/.local/share/orca/orca-wsl-bridge.ps1'
)
})
it('reports installed WSL launchers whose bin directory is missing from PATH', async () => {
const launcher = _internals.buildWslLauncher(
'C:\\Orca\\orca.cmd',
'/home/alice/.local/share/orca/orca-wsl-bridge.ps1'
)
const wsl = createWslRunner(launcher, false)
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus('C:\\Orca\\orca.cmd') },
wslRunner: wsl.runner
})
await expect(installer.getStatus()).resolves.toMatchObject({
state: 'installed',
pathConfigured: false,
detail: expect.stringContaining('not on PATH')
})
})
it('accepts current managed WSL scripts with an extra heredoc trailing newline', async () => {
const launcher = `${_internals.buildWslLauncher(
'C:\\Orca\\orca.cmd',
'/home/alice/.local/share/orca/orca-wsl-bridge.ps1'
)}\n`
const wsl = createWslRunner(launcher)
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus('C:\\Orca\\orca.cmd') },
wslRunner: async (distro, command) => {
if (command.includes('cat /home/alice/.local/share/orca/orca-wsl-bridge.ps1')) {
return `${_internals.buildWslBridgeScript()}\n`
}
return wsl.runner(distro, command)
}
})
await expect(installer.getStatus()).resolves.toMatchObject({
state: 'installed',
currentTarget: 'C:\\Orca\\orca.cmd'
})
})
it('refuses to replace an unmanaged WSL command', async () => {
const wsl = createWslRunner('#!/usr/bin/env bash\necho elsewhere\n')
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus() },
wslRunner: wsl.runner
})
await expect(installer.getStatus()).resolves.toMatchObject({ state: 'conflict' })
await expect(installer.install()).rejects.toThrow('Refusing to replace')
})
it('removes a managed WSL launcher', async () => {
const wsl = createWslRunner(
_internals.buildWslLauncher(
'C:\\Orca\\orca.cmd',
'/home/alice/.local/share/orca/orca-wsl-bridge.ps1'
)
)
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus('C:\\Orca\\orca.cmd') },
wslRunner: wsl.runner
})
await expect(installer.remove()).resolves.toMatchObject({ state: 'not_installed' })
expect(wsl.getFile()).toBeNull()
})
it('generates a launcher that forwards arguments through a PowerShell file bridge', () => {
const launcher = _internals.buildWslLauncher(
'C:\\Program Files\\Orca\\orca.cmd',
'/home/alice/.local/share/orca/orca-wsl-bridge.ps1'
)
const bridge = _internals.buildWslBridgeScript()
expect(launcher).toContain('command -v powershell.exe')
expect(launcher).toContain('/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe')
expect(launcher).toContain(
'Orca WSL CLI requires Windows interop and could not find powershell.exe.'
)
expect(launcher).toContain('"$ORCA_POWERSHELL" -NoProfile -ExecutionPolicy Bypass -File')
expect(launcher).toContain('ORCA_WSL_CWD=$(pwd -P 2>/dev/null) || {')
expect(launcher).toContain('ORCA_WSL_CWD=/')
expect(launcher).toContain('cd /')
expect(launcher).toContain('ORCA_WSL_CWD_WIN=$(wslpath -w "$ORCA_WSL_CWD")')
expect(launcher.indexOf('ORCA_WSL_CWD=$(pwd -P')).toBeLessThan(
launcher.indexOf('ORCA_BRIDGE_PS1_WIN=$(wslpath')
)
expect(launcher).toContain('"$ORCA_WIN_LAUNCHER" -WslCwd "$ORCA_WSL_CWD_WIN" "$@"')
expect(launcher).not.toContain('-Command')
expect(bridge).toContain('[CmdletBinding(PositionalBinding=$false)]')
expect(bridge).toContain('[Parameter(Mandatory=$true, Position=0)]')
expect(bridge).toContain('[string]$WslCwd')
expect(bridge).toContain('[Parameter(ValueFromRemainingArguments=$true)]')
expect(bridge).toContain('if ([string]::IsNullOrEmpty($WslCwd))')
expect(bridge).toContain('$env:ORCA_CLI_CWD = $WslCwd')
expect(bridge).toContain('Push-Location -LiteralPath (Split-Path -Parent $OrcaLauncher)')
expect(bridge).toContain('& $OrcaLauncher @ForwardArgs')
const nullExitCodeBranch = bridge.indexOf('if ($null -eq $LASTEXITCODE)')
const invocationFailureBranch = bridge.indexOf('if (-not $?)')
expect(nullExitCodeBranch).toBeGreaterThan(-1)
// Why: native launchers can set a non-zero LASTEXITCODE while $? is false;
// checking the native status first preserves that specific exit code.
expect(nullExitCodeBranch).toBeLessThan(invocationFailureBranch)
expect(bridge).toContain('$exitCode = $LASTEXITCODE')
expect(bridge).toContain('Remove-Item Env:ORCA_CLI_CWD -ErrorAction SilentlyContinue')
expect(bridge).toContain('catch')
expect(bridge).toContain('$exitCode = 1')
expect(bridge).toContain('exit $exitCode')
})
it('wraps WSL bash scripts as a single encoded command line', () => {
const command = [
'set -euo pipefail',
`cat > "$command_tmp" <<'ORCA_WSL_CLI'`,
'#!/usr/bin/env bash',
'exec powershell.exe "$@"',
'ORCA_WSL_CLI'
].join('\n')
const wrapped = _internals.buildEncodedWslBashCommand(command)
const encoded = wrapped.match(
/^set -o pipefail; printf %s '([^']+)' \| base64 -d \| bash$/
)?.[1]
expect(wrapped).not.toContain('\n')
expect(wrapped).toContain('set -o pipefail;')
expect(encoded).toBeTruthy()
expect(Buffer.from(encoded as string, 'base64').toString('utf8')).toBe(command)
})
it('treats absolute Windows PowerShell as interop-ready when powershell.exe is missing from PATH', async () => {
const wsl = createWslRunner()
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus() },
wslRunner: async (distro, command) => {
if (command.includes('command -v powershell.exe') && !command.includes('cat >')) {
expect(command).toContain('/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe')
return 'yes'
}
return wsl.runner(distro, command)
}
})
await expect(installer.getStatus()).resolves.toMatchObject({
state: 'not_installed',
commandPath: '/home/alice/.local/bin/orca-ide'
})
})
it('marks stale managed launchers that point at the old app bin instead of packaged resources', async () => {
const oldLauncher = _internals.buildWslLauncher(
'C:\\Users\\me\\AppData\\Local\\Programs\\Orca\\bin\\orca.cmd',
'/home/alice/.local/share/orca/orca-wsl-bridge.ps1'
)
const wsl = createWslRunner(oldLauncher)
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus() },
wslRunner: wsl.runner
})
await expect(installer.getStatus()).resolves.toMatchObject({
state: 'stale',
currentTarget: 'C:\\Users\\me\\AppData\\Local\\Programs\\Orca\\bin\\orca.cmd',
launcherPath: 'C:\\Users\\me\\AppData\\Local\\Programs\\Orca\\resources\\bin\\orca.exe'
})
await expect(installer.install()).resolves.toMatchObject({
state: 'installed',
currentTarget: 'C:\\Users\\me\\AppData\\Local\\Programs\\Orca\\resources\\bin\\orca.exe'
})
})
it('repairs the frozen pre-rc4 registration so orchestration send/reply reach native rc4', async () => {
const nativeLauncher = 'C:\\Program Files\\Orca\\resources\\bin\\orca.exe'
const wsl = createWslRunner(PRE_RC4_MANAGED_WSL_LAUNCHER)
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus(nativeLauncher) },
wslRunner: wsl.runner
})
const orchestrationCalls = [
['orchestration', 'send', '--type', 'heartbeat'],
['orchestration', 'send', '--type', 'worker_done'],
['orchestration', 'reply', '--message', 'line one\nline two']
]
const simulateRc4Launch = (args: string[]): number => {
const target = _internals.parseManagedLauncherTarget(wsl.getFile() ?? '')
return target?.toLowerCase().endsWith('orca.cmd') &&
args[0] === 'orchestration' &&
(args[1] === 'send' || args[1] === 'reply')
? 2
: 0
}
expect(orchestrationCalls.map(simulateRc4Launch)).toEqual([2, 2, 2])
await expect(
reconcileManagedWslCliRegistrations({
platform: 'win32',
isPackaged: true,
userDataPath: '/user-data',
listDistros: async () => ['Ubuntu'],
registry: {
getCandidates: async () => ['Ubuntu'],
recordObservations: async () => undefined
},
createInstaller: () => installer
})
).resolves.toEqual([
{ distro: 'Ubuntu', outcome: 'repaired', state: 'installed', managed: true }
])
await expect(installer.getStatus()).resolves.toMatchObject({
state: 'installed',
currentTarget: nativeLauncher
})
expect(orchestrationCalls.map(simulateRc4Launch)).toEqual([0, 0, 0])
})
it('leaves unmanaged WSL commands and conflicting bridges untouched during automatic repair', async () => {
const unmanaged = '#!/usr/bin/env bash\necho user-owned\n'
const wsl = createWslRunner(unmanaged)
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus() },
wslRunner: wsl.runner
})
await expect(installer.repairManagedRegistration()).resolves.toMatchObject({
changed: false,
status: { state: 'conflict' }
})
expect(wsl.getFile()).toBe(unmanaged)
expect(wsl.calls.some((command) => command.includes('cat > "$command_tmp"'))).toBe(false)
})
it('repairs a managed launcher whose bridge is missing, but preserves a conflicting bridge', async () => {
const nativeLauncher = 'C:\\Orca\\resources\\bin\\orca.exe'
const missingBridge = createWslRunner(PRE_RC4_MANAGED_WSL_LAUNCHER, true, {
initialBridge: null
})
const missingBridgeInstaller = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus(nativeLauncher) },
wslRunner: missingBridge.runner
})
await expect(missingBridgeInstaller.repairManagedRegistration()).resolves.toMatchObject({
changed: true,
status: { state: 'installed' }
})
expect(missingBridge.getBridge()).toBe(_internals.buildWslBridgeScript())
const staleBridge = createWslRunner(PRE_RC4_MANAGED_WSL_LAUNCHER, true, {
initialBridge: '# Orca managed WSL CLI PowerShell bridge\nWrite-Output "stale"\n'
})
const staleBridgeInstaller = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus(nativeLauncher) },
wslRunner: staleBridge.runner
})
await expect(staleBridgeInstaller.repairManagedRegistration()).resolves.toMatchObject({
changed: true,
status: { state: 'installed' }
})
expect(staleBridge.getBridge()).toBe(_internals.buildWslBridgeScript())
const conflictingBridge = createWslRunner(PRE_RC4_MANAGED_WSL_LAUNCHER, true, {
initialBridge: 'Write-Output "user-owned bridge"\n'
})
const conflictingBridgeInstaller = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus(nativeLauncher) },
wslRunner: conflictingBridge.runner
})
// Why: a stale launcher with a user-owned bridge must surface as a
// non-throwing conflict, not retry a doomed install on every startup.
await expect(conflictingBridgeInstaller.repairManagedRegistration()).resolves.toMatchObject({
changed: false,
managed: true,
status: { state: 'conflict' }
})
expect(conflictingBridge.getBridge()).toBe('Write-Output "user-owned bridge"\n')
expect(conflictingBridge.getFile()).toBe(PRE_RC4_MANAGED_WSL_LAUNCHER)
expect(
conflictingBridge.calls.some((command) => command.includes('cat > "$command_tmp"'))
).toBe(false)
})
it('retains command ownership when only the bridge conflicts', async () => {
const nativeLauncher = 'C:\\Orca\\resources\\bin\\orca.exe'
const currentLauncher = _internals.buildWslLauncher(
nativeLauncher,
'/home/alice/.local/share/orca/orca-wsl-bridge.ps1'
)
const wsl = createWslRunner(currentLauncher, true, {
initialBridge: 'Write-Output "user-owned bridge"\n'
})
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus(nativeLauncher) },
wslRunner: wsl.runner
})
await expect(installer.repairManagedRegistration()).resolves.toMatchObject({
changed: false,
managed: true,
status: { state: 'conflict' }
})
expect(wsl.getBridge()).toBe('Write-Output "user-owned bridge"\n')
expect(wsl.getFile()).toBe(currentLauncher)
})
it('moves a legacy-only managed registration to orca-ide without touching unmanaged names', async () => {
const nativeLauncher = 'C:\\Orca\\resources\\bin\\orca.exe'
const managedLegacy = createWslRunner(null, true, {
initialBridge: _internals.buildWslBridgeScript(),
initialLegacyFile: PRE_RC4_MANAGED_WSL_LAUNCHER
})
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus(nativeLauncher) },
wslRunner: managedLegacy.runner
})
await expect(installer.repairManagedRegistration()).resolves.toMatchObject({
changed: true,
status: { state: 'installed', currentTarget: nativeLauncher }
})
expect(managedLegacy.getLegacyFile()).toBeNull()
const unmanagedLegacy = createWslRunner(null, true, {
initialLegacyFile: '#!/bin/sh\necho user-owned\n'
})
const unmanagedInstaller = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus(nativeLauncher) },
wslRunner: unmanagedLegacy.runner
})
await expect(unmanagedInstaller.repairManagedRegistration()).resolves.toMatchObject({
changed: false,
status: { state: 'not_installed' }
})
expect(unmanagedLegacy.getLegacyFile()).toBe('#!/bin/sh\necho user-owned\n')
})
it('does not adopt a legacy-managed registration when the bridge is user-owned', async () => {
const wsl = createWslRunner(null, true, {
initialBridge: 'Write-Output "user-owned bridge"\n',
initialLegacyFile: PRE_RC4_MANAGED_WSL_LAUNCHER
})
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus() },
wslRunner: wsl.runner
})
// Why: adoption would fail install()'s bridge guard on every startup;
// repair must report blocked-but-managed instead of a doomed install.
await expect(installer.repairManagedRegistration()).resolves.toMatchObject({
changed: false,
managed: true,
status: { state: 'not_installed' }
})
expect(wsl.getLegacyFile()).toBe(PRE_RC4_MANAGED_WSL_LAUNCHER)
expect(wsl.calls.some((command) => command.includes('cat > "$command_tmp"'))).toBe(false)
})
it('removes the managed legacy launcher on removal so reconciliation cannot re-adopt it', async () => {
const nativeLauncher = 'C:\\Orca\\resources\\bin\\orca.exe'
const managedLegacy = createWslRunner(null, true, {
initialBridge: _internals.buildWslBridgeScript(),
initialLegacyFile: PRE_RC4_MANAGED_WSL_LAUNCHER
})
const managedLegacyInstaller = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus(nativeLauncher) },
wslRunner: managedLegacy.runner
})
await expect(managedLegacyInstaller.remove()).resolves.toMatchObject({
state: 'not_installed'
})
expect(managedLegacy.getLegacyFile()).toBeNull()
const unmanagedLegacy = createWslRunner(null, true, {
initialLegacyFile: '#!/bin/sh\necho user-owned\n'
})
const unmanagedInstaller2 = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus(nativeLauncher) },
wslRunner: unmanagedLegacy.runner
})
await expect(unmanagedInstaller2.remove()).resolves.toMatchObject({
state: 'not_installed'
})
expect(unmanagedLegacy.getLegacyFile()).toBe('#!/bin/sh\necho user-owned\n')
const installedWithLegacy = createWslRunner(
_internals.buildWslLauncher(
nativeLauncher,
'/home/alice/.local/share/orca/orca-wsl-bridge.ps1'
),
true,
{ initialLegacyFile: PRE_RC4_MANAGED_WSL_LAUNCHER }
)
const installedInstaller = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus(nativeLauncher) },
wslRunner: installedWithLegacy.runner
})
await expect(installedInstaller.remove()).resolves.toMatchObject({ state: 'not_installed' })
expect(installedWithLegacy.getFile()).toBeNull()
expect(installedWithLegacy.getLegacyFile()).toBeNull()
})
it('keeps the pre-rc4 files on a transactional replacement failure', async () => {
const bridge = _internals.buildWslBridgeScript()
const wsl = createWslRunner(PRE_RC4_MANAGED_WSL_LAUNCHER, true, {
initialBridge: bridge,
failInstall: true
})
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: {
getStatus: async () => makeHostStatus('C:\\Program Files\\Orca\\resources\\bin\\orca.exe')
},
wslRunner: wsl.runner
})
await expect(installer.repairManagedRegistration()).rejects.toThrow(
'simulated replacement failure'
)
expect(wsl.getFile()).toBe(PRE_RC4_MANAGED_WSL_LAUNCHER)
expect(wsl.getBridge()).toBe(bridge)
const installCommand = wsl.calls.find((command) => command.includes('cat > "$command_tmp"'))
expect(installCommand).toContain('rollback() {')
expect(installCommand).toContain('set +e')
expect(installCommand).toContain('bridge_backup="${bridge_tmp}.backup"')
expect(installCommand).toContain('cp -p')
expect(installCommand).toContain('elif [ "$bridge_touched" -eq 1 ]')
expect(installCommand).toContain('committed=1')
expect(installCommand).toContain('flock -x -w 30 9')
// Why: the command replace must stay one atomic rename; a mv-based backup
// would leave a window where a concurrent shell finds no orca-ide at all.
expect(installCommand).not.toContain('command_backup')
expect(installCommand).not.toContain(`mv -f '/home/alice/.local/bin/orca-ide'`)
})
it.skipIf(process.platform === 'win32')(
'rolls both files back when the command replacement fails after the bridge move',
async () => {
const root = await mkdtemp(join(tmpdir(), 'orca-wsl-cli-rollback-'))
const home = join(root, 'home with spaces')
const commandPath = join(home, '.local', 'bin', 'orca-ide')
const bridgePath = join(home, '.local', 'share', 'orca', 'orca-wsl-bridge.ps1')
const bridge = _internals.buildWslBridgeScript()
await mkdir(join(home, '.local', 'bin'), { recursive: true })
await mkdir(join(home, '.local', 'share', 'orca'), { recursive: true })
await writeFile(commandPath, PRE_RC4_MANAGED_WSL_LAUNCHER, 'utf8')
await writeFile(bridgePath, bridge, 'utf8')
const runner = async (_distro: string, command: string): Promise<string> => {
if (command.includes('printf %s "$HOME"')) {
return home
}
if (command.includes('cat > "$command_tmp"')) {
const executableCommand = command
.split('\n')
.map((line) => (line.startsWith('mv -f "$command_tmp" ') ? 'exit 71' : line))
.join('\n')
return execFileSync('bash', ['-c', executableCommand], { encoding: 'utf8' })
}
if (command.includes('command -v powershell.exe')) {
return 'yes'
}
if (command.includes('case ":$PATH:"')) {
return 'yes'
}
return execFileSync('bash', ['-c', command], { encoding: 'utf8' })
}
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: {
getStatus: async () => makeHostStatus('C:\\Program Files\\Orca\\resources\\bin\\orca.exe')
},
wslRunner: runner
})
try {
await expect(installer.repairManagedRegistration()).rejects.toThrow()
await expect(readFile(commandPath, 'utf8')).resolves.toBe(PRE_RC4_MANAGED_WSL_LAUNCHER)
await expect(readFile(bridgePath, 'utf8')).resolves.toBe(bridge)
} finally {
await rm(root, { recursive: true, force: true })
}
}
)
it('skips automatic repair when WSL interop is unavailable', async () => {
const wsl = createWslRunner(PRE_RC4_MANAGED_WSL_LAUNCHER, true, { interopReady: false })
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus() },
wslRunner: wsl.runner
})
await expect(installer.repairManagedRegistration()).resolves.toMatchObject({
changed: false,
status: { state: 'unsupported' }
})
expect(wsl.getFile()).toBe(PRE_RC4_MANAGED_WSL_LAUNCHER)
})
it('is idempotent after repairing an old managed registration', async () => {
const nativeLauncher = 'D:\\Custom Orca\\resources\\bin\\orca.exe'
const wsl = createWslRunner(PRE_RC4_MANAGED_WSL_LAUNCHER)
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus(nativeLauncher) },
wslRunner: wsl.runner
})
await expect(installer.repairManagedRegistration()).resolves.toMatchObject({ changed: true })
await expect(installer.repairManagedRegistration()).resolves.toMatchObject({ changed: false })
expect(wsl.calls.filter((command) => command.includes('cat > "$command_tmp"'))).toHaveLength(1)
expect(wsl.getFile()).toContain("ORCA_WIN_LAUNCHER='D:\\Custom Orca\\resources\\bin\\orca.exe'")
})
it('settles when wsl.exe never reports completion', async () => {
vi.useFakeTimers()
const killMock = vi.fn()
execFileMock.mockImplementation(() => ({ kill: killMock }))
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus() }
})
const promise = installer.getStatus()
let settled = false
void promise
.catch(() => undefined)
.finally(() => {
settled = true
})
await vi.advanceTimersByTimeAsync(10_000)
await Promise.resolve()
expect(settled).toBe(true)
await expect(promise).rejects.toThrow('WSL command timed out')
expect(killMock).toHaveBeenCalled()
})
it('refuses to remove an old managed launcher when the bridge path is user-owned', async () => {
const oldLauncher = _internals.buildWslLauncher(
'C:\\Old\\orca.cmd',
'/home/alice/.local/share/orca/orca-wsl-bridge.ps1'
)
const wsl = createWslRunner(oldLauncher)
const installer = new WslCliInstaller({
platform: 'win32',
distro: 'Ubuntu',
hostInstaller: { getStatus: async () => makeHostStatus('C:\\Orca\\orca.cmd') },
wslRunner: async (distro, command) => {
if (command.includes('cat /home/alice/.local/share/orca/orca-wsl-bridge.ps1')) {
return 'user bridge'
}
if (command.includes('rm -f')) {
throw new Error('__ORCA_CONFLICT__')
}
return wsl.runner(distro, command)
}
})
await expect(installer.remove()).rejects.toThrow('__ORCA_CONFLICT__')
})
})