mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 00:03:20 +00:00
fix(ssh): keep Windows CLI when the launcher compiler is missing
The remote CLI installer deleted the legacy orca.cmd bridge before it confirmed csc.exe existed, so a minimal Windows host without the .NET compiler lost its existing CLI command and got nothing back. Move the legacy-shim removal to run only after the compile and the launcher-existence guard both pass. Missing-compiler and compile-failure paths now exit non-zero while leaving the existing orca.cmd untouched; a successful upgrade still clears the unsafe %* bridge (orca.exe already shadows orca.cmd via PATHEXT). No %* forwarding is restored. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Buffer } from 'node:buffer'
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
@@ -23,15 +23,18 @@ function decodePowerShellCommand(command: string): string {
|
||||
}
|
||||
|
||||
describe('SSH remote Orca CLI launcher', () => {
|
||||
it('compiles a native Windows launcher without a cmd.exe argument bridge', () => {
|
||||
const hostPlatform = getRemoteHostPlatform('win32-x64')
|
||||
const plan = createRemoteCliInstallPlan({
|
||||
function windowsInstallPlan(): ReturnType<typeof createRemoteCliInstallPlan> {
|
||||
return createRemoteCliInstallPlan({
|
||||
binDir: 'C:/Users/me user/.orca-relay/bin',
|
||||
relayDir: 'C:/Users/me user/.orca-remote/relay-v1',
|
||||
nodePath: 'C:/Program Files/nodejs/node.exe',
|
||||
sockPath: '\\\\.\\pipe\\orca-relay-123',
|
||||
hostPlatform
|
||||
hostPlatform: getRemoteHostPlatform('win32-x64')
|
||||
})
|
||||
}
|
||||
|
||||
it('compiles a native Windows launcher without a cmd.exe argument bridge', () => {
|
||||
const plan = windowsInstallPlan()
|
||||
|
||||
expect(plan.launcherPath).toBe('C:/Users/me user/.orca-relay/bin/orca.exe')
|
||||
expect(plan.files).toHaveLength(1)
|
||||
@@ -54,7 +57,24 @@ describe('SSH remote Orca CLI launcher', () => {
|
||||
expect(compileScript).toContain('/out:orca.exe')
|
||||
expect(compileScript).toContain('C:/Users/me user/.orca-relay/bin/orca-launcher.cs')
|
||||
expect(compileScript).toContain('C:/Users/me user/.orca-relay/bin/orca.cmd')
|
||||
expect(compileScript.indexOf('orca.cmd')).toBeLessThan(compileScript.indexOf('csc.exe'))
|
||||
})
|
||||
|
||||
it('removes the legacy orca.cmd only after every compile guard has passed', () => {
|
||||
const script = decodePowerShellCommand(windowsInstallPlan().postWriteCommands[0] ?? '')
|
||||
const legacyShimRemoval =
|
||||
"Remove-Item -LiteralPath 'C:/Users/me user/.orca-relay/bin/orca.cmd' -Force -ErrorAction SilentlyContinue"
|
||||
// Why: a host missing csc.exe or failing the compile must keep its existing
|
||||
// CLI, so every fail-closed guard precedes the legacy %* shim removal.
|
||||
const guards = [
|
||||
"if (-not $compiler) { Write-Error 'Unable to find the .NET Framework C# compiler required for the Orca SSH CLI launcher.'; exit 1 }",
|
||||
'if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }',
|
||||
"if (-not (Test-Path -LiteralPath 'C:/Users/me user/.orca-relay/bin/orca.exe' -PathType Leaf))"
|
||||
]
|
||||
expect(script).toContain(legacyShimRemoval)
|
||||
for (const guard of guards) {
|
||||
expect(script).toContain(guard)
|
||||
expect(script.indexOf(guard)).toBeLessThan(script.indexOf(legacyShimRemoval))
|
||||
}
|
||||
})
|
||||
|
||||
itWindows('preserves a multiline argument through the compiled remote launcher', () => {
|
||||
@@ -128,6 +148,51 @@ describe('SSH remote Orca CLI launcher', () => {
|
||||
}
|
||||
})
|
||||
|
||||
itWindows('preserves the existing orca.cmd when the compiler is missing', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'orca remote cli '))
|
||||
try {
|
||||
const binDir = join(root, 'bin').replaceAll('\\', '/')
|
||||
mkdirSync(binDir, { recursive: true })
|
||||
const legacyShimPath = join(binDir, 'orca.cmd')
|
||||
writeFileSync(legacyShimPath, '@echo legacy orca cli\r\n', 'utf8')
|
||||
|
||||
const plan = createRemoteCliInstallPlan({
|
||||
binDir,
|
||||
relayDir: join(root, 'relay').replaceAll('\\', '/'),
|
||||
nodePath: process.execPath,
|
||||
sockPath: '\\\\.\\pipe\\orca-relay-test',
|
||||
hostPlatform: getRemoteHostPlatform('win32-x64')
|
||||
})
|
||||
for (const file of plan.files) {
|
||||
mkdirSync(dirname(file.path), { recursive: true })
|
||||
writeFileSync(file.path, file.contents, 'utf8')
|
||||
}
|
||||
|
||||
const encoded = plan.postWriteCommands[0]?.match(/-EncodedCommand\s+(\S+)/)?.[1]
|
||||
expect(encoded).toBeTruthy()
|
||||
// Point WINDIR at a directory with no csc.exe so compiler discovery fails.
|
||||
const compile = spawnSync(
|
||||
'powershell.exe',
|
||||
[
|
||||
'-NoProfile',
|
||||
'-NonInteractive',
|
||||
'-ExecutionPolicy',
|
||||
'Bypass',
|
||||
'-EncodedCommand',
|
||||
encoded!
|
||||
],
|
||||
{ encoding: 'utf8', env: { ...process.env, WINDIR: root, SystemRoot: root } }
|
||||
)
|
||||
|
||||
expect(compile.status).not.toBe(0)
|
||||
expect(existsSync(legacyShimPath), 'existing orca.cmd must survive a failed install').toBe(
|
||||
true
|
||||
)
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps the POSIX launcher as an argv-preserving shell exec', () => {
|
||||
const plan = createRemoteCliInstallPlan({
|
||||
binDir: '/home/me/.orca-relay/bin',
|
||||
|
||||
@@ -169,9 +169,6 @@ function createWindowsLauncherCompileCommand(
|
||||
.join(' ')
|
||||
return powerShellCommand(
|
||||
[
|
||||
// Why: an upgrade must not leave the old %* batch bridge callable when
|
||||
// compiler discovery fails; losing the convenience CLI is safer.
|
||||
`Remove-Item -LiteralPath ${powerShellLiteral(legacyShimPath)} -Force -ErrorAction SilentlyContinue`,
|
||||
`Set-Location -ErrorAction Stop -LiteralPath ${powerShellLiteral(binDir)}`,
|
||||
'$windowsDirectory = if ($env:WINDIR) { $env:WINDIR } else { $env:SystemRoot }',
|
||||
`$compilerCandidates = @((Join-Path $windowsDirectory 'Microsoft.NET\\Framework64\\v4.0.30319\\csc.exe'), (Join-Path $windowsDirectory 'Microsoft.NET\\Framework\\v4.0.30319\\csc.exe'))`,
|
||||
@@ -180,6 +177,9 @@ function createWindowsLauncherCompileCommand(
|
||||
`& $compiler ${compilerArgs}`,
|
||||
'if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }',
|
||||
`if (-not (Test-Path -LiteralPath ${powerShellLiteral(launcherPath)} -PathType Leaf)) { Write-Error 'The Orca SSH CLI launcher compiler produced no executable.'; exit 1 }`,
|
||||
// Why: remove the legacy %* bridge only after a successful compile, so a
|
||||
// host missing csc.exe keeps its existing CLI (orca.exe shadows orca.cmd).
|
||||
`Remove-Item -LiteralPath ${powerShellLiteral(legacyShimPath)} -Force -ErrorAction SilentlyContinue`,
|
||||
`Remove-Item -LiteralPath ${powerShellLiteral(sourcePath)} -Force`
|
||||
].join('; ')
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user