fix: detect noisy Windows ARM64 SSH platforms (#7965)
* fix: detect noisy Windows ARM64 SSH platforms - Scan remote platform probe output line-by-line for supported OS/arch markers. - Handle PowerShell/OpenSSH first-use CLIXML or banner noise before Windows ARM64. - Add regression coverage for noisy Windows ARM64 detection. * fix: harden SSH platform probe markers --------- Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
This commit is contained in:
parent
4df9457530
commit
e3f0b99f4a
|
|
@ -98,7 +98,7 @@ describe('cross-version isolation', () => {
|
|||
// $HOME, isRelayAlreadyInstalled probe, lock acquire, upload (no exec),
|
||||
// npm install, finalize, socket probe, socket poll, then GC scan.
|
||||
const responses: string[] = [
|
||||
'Linux x86_64', // uname -sm
|
||||
'__ORCA_REMOTE_PLATFORM__ Linux x86_64', // tagged POSIX platform probe
|
||||
'/home/u', // echo $HOME
|
||||
'MISSING', // isRelayAlreadyInstalled (v2 dir doesn't exist)
|
||||
'', // mkdir -p remoteRelayDir (v2)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ vi.mock('./ssh-relay-deploy-helpers', () => ({
|
|||
onData: vi.fn(),
|
||||
onClose: vi.fn()
|
||||
}),
|
||||
execCommand: vi.fn().mockResolvedValue('Linux x86_64')
|
||||
execCommand: vi.fn().mockResolvedValue('__ORCA_REMOTE_PLATFORM__ Linux x86_64')
|
||||
}))
|
||||
|
||||
vi.mock('./ssh-remote-node-resolution', () => ({
|
||||
|
|
@ -123,7 +123,7 @@ describe('deployAndLaunchRelay', () => {
|
|||
it('calls exec to detect remote platform', async () => {
|
||||
const conn = makeMockConnection()
|
||||
const mockExecCommand = vi.mocked(execCommand)
|
||||
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
|
||||
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
|
||||
mockExecCommand.mockResolvedValueOnce('/home/user') // echo $HOME
|
||||
mockExecCommand.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK') // native deps probe
|
||||
mockExecCommand.mockResolvedValueOnce('DEAD') // socket probe
|
||||
|
|
@ -131,13 +131,16 @@ describe('deployAndLaunchRelay', () => {
|
|||
|
||||
await deployAndLaunchRelay(conn)
|
||||
|
||||
expect(mockExecCommand).toHaveBeenCalledWith(conn, 'uname -sm')
|
||||
expect(mockExecCommand).toHaveBeenCalledWith(
|
||||
conn,
|
||||
"printf '\\n%s ' '__ORCA_REMOTE_PLATFORM__'; uname -sm"
|
||||
)
|
||||
})
|
||||
|
||||
it('reports progress via callback', async () => {
|
||||
const conn = makeMockConnection()
|
||||
const mockExecCommand = vi.mocked(execCommand)
|
||||
mockExecCommand.mockResolvedValueOnce('Linux x86_64')
|
||||
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64')
|
||||
mockExecCommand.mockResolvedValueOnce('/home/user')
|
||||
mockExecCommand.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK') // native deps probe
|
||||
mockExecCommand.mockResolvedValueOnce('DEAD') // socket probe
|
||||
|
|
@ -153,7 +156,7 @@ describe('deployAndLaunchRelay', () => {
|
|||
it('resolves the remote node path once per deploy', async () => {
|
||||
const conn = makeMockConnection()
|
||||
const mockExecCommand = vi.mocked(execCommand)
|
||||
mockExecCommand.mockResolvedValueOnce('Linux x86_64')
|
||||
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64')
|
||||
mockExecCommand.mockResolvedValueOnce('/home/user')
|
||||
mockExecCommand.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK')
|
||||
mockExecCommand.mockResolvedValueOnce('DEAD')
|
||||
|
|
@ -167,7 +170,7 @@ describe('deployAndLaunchRelay', () => {
|
|||
it('resolves node concurrently with remote home, not after the install-state chain', async () => {
|
||||
const conn = makeMockConnection()
|
||||
const mockExecCommand = vi.mocked(execCommand)
|
||||
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
|
||||
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
|
||||
|
||||
let markNodeResolutionStarted: () => void = () => {}
|
||||
const nodeResolutionStarted = new Promise<void>((resolve) => {
|
||||
|
|
@ -221,7 +224,7 @@ describe('deployAndLaunchRelay', () => {
|
|||
const conn = makeMockConnection()
|
||||
vi.mocked(conn.canRunConcurrentExecCommands).mockReturnValue(false)
|
||||
const mockExecCommand = vi.mocked(execCommand)
|
||||
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
|
||||
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
|
||||
let releaseRemoteHome: (home: string) => void = () => {}
|
||||
let remoteHomeProbeStarted: () => void = () => {}
|
||||
const remoteHomeProbeStartedPromise = new Promise<void>((resolve) => {
|
||||
|
|
@ -274,7 +277,7 @@ describe('deployAndLaunchRelay', () => {
|
|||
fallbackInstallStateCompleted = true
|
||||
return true
|
||||
})
|
||||
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
|
||||
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
|
||||
mockExecCommand.mockResolvedValueOnce('/home/user') // concurrent install-state $HOME
|
||||
mockExecCommand.mockRejectedValueOnce(sessionLimitError) // concurrent node path probe
|
||||
mockExecCommand.mockResolvedValueOnce('/home/user') // sequential fallback $HOME
|
||||
|
|
@ -302,7 +305,7 @@ describe('deployAndLaunchRelay', () => {
|
|||
throw sessionLimitError
|
||||
})
|
||||
.mockResolvedValueOnce(true)
|
||||
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
|
||||
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
|
||||
mockExecCommand.mockResolvedValueOnce('/home/user') // concurrent install-state $HOME
|
||||
mockExecCommand.mockResolvedValueOnce('/home/user') // sequential fallback $HOME
|
||||
mockExecCommand.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK') // native deps probe
|
||||
|
|
@ -324,7 +327,7 @@ describe('deployAndLaunchRelay', () => {
|
|||
const mockExecCommand = vi.mocked(execCommand)
|
||||
const nodeError = new Error('Node.js not found on remote host')
|
||||
vi.mocked(resolveRemoteNodePath).mockRejectedValueOnce(nodeError)
|
||||
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
|
||||
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
|
||||
mockExecCommand.mockResolvedValueOnce('/home/user') // concurrent install-state $HOME
|
||||
|
||||
await expect(deployAndLaunchRelay(conn)).rejects.toBe(nodeError)
|
||||
|
|
@ -346,7 +349,7 @@ describe('deployAndLaunchRelay', () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
|
||||
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
|
||||
mockExecCommand.mockResolvedValueOnce('relative-home') // invalid install-state $HOME
|
||||
|
||||
const timedDeploy = Promise.race([
|
||||
|
|
@ -370,7 +373,7 @@ describe('deployAndLaunchRelay', () => {
|
|||
const installError = new Error('permission denied while checking relay install')
|
||||
vi.mocked(resolveRemoteNodePath).mockRejectedValueOnce(sessionLimitError)
|
||||
vi.mocked(isRelayAlreadyInstalled).mockRejectedValueOnce(installError)
|
||||
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
|
||||
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
|
||||
mockExecCommand.mockResolvedValueOnce('/home/user') // concurrent install-state $HOME
|
||||
|
||||
await expect(deployAndLaunchRelay(conn)).rejects.toBe(installError)
|
||||
|
|
@ -384,7 +387,7 @@ describe('deployAndLaunchRelay', () => {
|
|||
const sessionLimitError = Object.assign(new Error('(SSH) Channel open failure: open failed'), {
|
||||
reason: 4
|
||||
})
|
||||
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
|
||||
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
|
||||
let releaseRemoteHome: (home: string) => void = () => {}
|
||||
let remoteHomeSettled = false
|
||||
mockExecCommand.mockReturnValueOnce(
|
||||
|
|
@ -420,7 +423,7 @@ describe('deployAndLaunchRelay', () => {
|
|||
it('defaults fresh relays to keep-alive-until-reset', async () => {
|
||||
const conn = makeMockConnection()
|
||||
const mockExecCommand = vi.mocked(execCommand)
|
||||
mockExecCommand.mockResolvedValueOnce('Linux x86_64')
|
||||
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64')
|
||||
mockExecCommand.mockResolvedValueOnce('/home/user')
|
||||
mockExecCommand.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK')
|
||||
mockExecCommand.mockResolvedValueOnce('DEAD')
|
||||
|
|
@ -439,7 +442,7 @@ describe('deployAndLaunchRelay', () => {
|
|||
it('allows an unlimited SSH disconnect grace window', async () => {
|
||||
const conn = makeMockConnection()
|
||||
const mockExecCommand = vi.mocked(execCommand)
|
||||
mockExecCommand.mockResolvedValueOnce('Linux x86_64')
|
||||
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64')
|
||||
mockExecCommand.mockResolvedValueOnce('/home/user')
|
||||
mockExecCommand.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK')
|
||||
mockExecCommand.mockResolvedValueOnce('DEAD')
|
||||
|
|
@ -458,7 +461,7 @@ describe('deployAndLaunchRelay', () => {
|
|||
it('clamps configured SSH disconnect grace to the seven-day maximum', async () => {
|
||||
const conn = makeMockConnection()
|
||||
const mockExecCommand = vi.mocked(execCommand)
|
||||
mockExecCommand.mockResolvedValueOnce('Linux x86_64')
|
||||
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64')
|
||||
mockExecCommand.mockResolvedValueOnce('/home/user')
|
||||
mockExecCommand.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK')
|
||||
mockExecCommand.mockResolvedValueOnce('DEAD')
|
||||
|
|
@ -477,7 +480,7 @@ describe('deployAndLaunchRelay', () => {
|
|||
it('uses a content-hashed versioned remote install directory', async () => {
|
||||
const conn = makeMockConnection()
|
||||
const mockExecCommand = vi.mocked(execCommand)
|
||||
mockExecCommand.mockResolvedValueOnce('Linux x86_64')
|
||||
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64')
|
||||
mockExecCommand.mockResolvedValueOnce('/home/user')
|
||||
mockExecCommand.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK')
|
||||
mockExecCommand.mockResolvedValueOnce('DEAD')
|
||||
|
|
@ -522,12 +525,12 @@ describe('deployAndLaunchRelay', () => {
|
|||
const connB = makeMockConnection()
|
||||
const mockExecCommand = vi.mocked(execCommand)
|
||||
mockExecCommand
|
||||
.mockResolvedValueOnce('Linux x86_64') // uname A
|
||||
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe A
|
||||
.mockResolvedValueOnce('/home/user') // $HOME A
|
||||
.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK') // native deps probe A
|
||||
.mockResolvedValueOnce('DEAD') // probe A
|
||||
.mockResolvedValueOnce('READY') // poll A
|
||||
.mockResolvedValueOnce('Linux x86_64') // uname B
|
||||
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe B
|
||||
.mockResolvedValueOnce('/home/user') // $HOME B
|
||||
.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK') // native deps probe B
|
||||
.mockResolvedValueOnce('DEAD') // probe B
|
||||
|
|
@ -561,8 +564,8 @@ describe('deployAndLaunchRelay', () => {
|
|||
const mockExecCommand = vi.mocked(execCommand)
|
||||
vi.mocked(resolveRemoteNodePath).mockResolvedValue('C:/Program Files/nodejs/node.exe')
|
||||
mockExecCommand
|
||||
.mockRejectedValueOnce(new Error('uname not found')) // uname -sm
|
||||
.mockResolvedValueOnce('Windows X64') // PowerShell platform probe
|
||||
.mockRejectedValueOnce(new Error('uname not found')) // tagged POSIX platform probe
|
||||
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Windows X64') // tagged PowerShell platform probe
|
||||
.mockResolvedValueOnce('C:\\Users\\me user') // remote home
|
||||
.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK') // native deps probe
|
||||
.mockResolvedValueOnce('') // no persisted active pipe
|
||||
|
|
@ -607,8 +610,8 @@ describe('deployAndLaunchRelay', () => {
|
|||
onClose: vi.fn()
|
||||
})
|
||||
mockExecCommand
|
||||
.mockRejectedValueOnce(new Error('uname not found')) // uname -sm
|
||||
.mockResolvedValueOnce('Windows X64') // PowerShell platform probe
|
||||
.mockRejectedValueOnce(new Error('uname not found')) // tagged POSIX platform probe
|
||||
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Windows X64') // tagged PowerShell platform probe
|
||||
.mockResolvedValueOnce('C:\\Users\\me user') // remote home
|
||||
.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK') // native deps probe
|
||||
.mockResolvedValueOnce('') // no persisted active pipe yet
|
||||
|
|
@ -654,8 +657,8 @@ describe('deployAndLaunchRelay', () => {
|
|||
const persistedPipe = '\\\\.\\pipe\\orca-relay-1234567890abcdef1234'
|
||||
vi.mocked(resolveRemoteNodePath).mockResolvedValue('C:/Program Files/nodejs/node.exe')
|
||||
mockExecCommand
|
||||
.mockRejectedValueOnce(new Error('uname not found')) // uname -sm
|
||||
.mockResolvedValueOnce('Windows X64') // PowerShell platform probe
|
||||
.mockRejectedValueOnce(new Error('uname not found')) // tagged POSIX platform probe
|
||||
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Windows X64') // tagged PowerShell platform probe
|
||||
.mockResolvedValueOnce('C:\\Users\\me user') // remote home
|
||||
.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK') // native deps probe
|
||||
.mockResolvedValueOnce(`${persistedPipe}\n`) // persisted active pipe marker
|
||||
|
|
@ -682,8 +685,8 @@ describe('deployAndLaunchRelay', () => {
|
|||
const mockExecCommand = vi.mocked(execCommand)
|
||||
vi.mocked(resolveRemoteNodePath).mockResolvedValue('C:/Program Files/nodejs/node.exe')
|
||||
mockExecCommand
|
||||
.mockRejectedValueOnce(new Error('uname not found')) // uname A
|
||||
.mockResolvedValueOnce('Windows X64')
|
||||
.mockRejectedValueOnce(new Error('uname not found')) // tagged POSIX platform probe A
|
||||
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Windows X64')
|
||||
.mockResolvedValueOnce('C:\\Users\\me user')
|
||||
.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK')
|
||||
.mockResolvedValueOnce('') // no persisted active pipe A
|
||||
|
|
@ -691,8 +694,8 @@ describe('deployAndLaunchRelay', () => {
|
|||
.mockResolvedValueOnce('')
|
||||
.mockResolvedValueOnce('READY')
|
||||
.mockResolvedValueOnce('') // persist active pipe A
|
||||
.mockRejectedValueOnce(new Error('uname not found')) // uname B
|
||||
.mockResolvedValueOnce('Windows X64')
|
||||
.mockRejectedValueOnce(new Error('uname not found')) // tagged POSIX platform probe B
|
||||
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Windows X64')
|
||||
.mockResolvedValueOnce('C:\\Users\\me user')
|
||||
.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK')
|
||||
.mockResolvedValueOnce('') // no persisted active pipe B
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ function makeExecResponses(opts: {
|
|||
// build toolchain — no chmod/probe/launch slots are reached.
|
||||
if (opts.npmInstall !== 'ok') {
|
||||
return [
|
||||
'Linux x86_64',
|
||||
'__ORCA_REMOTE_PLATFORM__ Linux x86_64',
|
||||
'/home/u',
|
||||
'', // mkdir remoteDir (uploadRelay)
|
||||
'', // chmod +x node
|
||||
|
|
@ -171,7 +171,7 @@ function makeExecResponses(opts: {
|
|||
? { reject: 'cd: no such file or directory' }
|
||||
: opts.probe
|
||||
const slots: ExecResponse[] = [
|
||||
'Linux x86_64',
|
||||
'__ORCA_REMOTE_PLATFORM__ Linux x86_64',
|
||||
'/home/u',
|
||||
'', // mkdir remoteDir (uploadRelay)
|
||||
'', // chmod +x node
|
||||
|
|
@ -530,7 +530,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => {
|
|||
vi.mocked(resolveRemoteNodePath).mockResolvedValueOnce('C:/Program Files/nodejs/node.exe')
|
||||
const conn = makeMockConnection(sftpCapture)
|
||||
feed([
|
||||
'Windows AMD64',
|
||||
'__ORCA_REMOTE_PLATFORM__ Windows AMD64',
|
||||
'C:\\Users\\u',
|
||||
'', // mkdir remoteDir
|
||||
'', // npm install native deps
|
||||
|
|
@ -605,7 +605,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => {
|
|||
vi.mocked(isRelayAlreadyInstalled).mockResolvedValue(true)
|
||||
const conn = makeMockConnection(sftpCapture)
|
||||
feed([
|
||||
'Linux x86_64',
|
||||
'__ORCA_REMOTE_PLATFORM__ Linux x86_64',
|
||||
'/home/u',
|
||||
'MISSING', // first native-deps probe before lock
|
||||
'MISSING', // re-probe after lock
|
||||
|
|
@ -632,7 +632,13 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => {
|
|||
it('does not mutate an existing relay dir when required native deps are present', async () => {
|
||||
vi.mocked(isRelayAlreadyInstalled).mockResolvedValue(true)
|
||||
const conn = makeMockConnection(sftpCapture)
|
||||
feed(['Linux x86_64', '/home/u', 'ORCA-NATIVE-DEPS-OK', 'DEAD', 'READY'])
|
||||
feed([
|
||||
'__ORCA_REMOTE_PLATFORM__ Linux x86_64',
|
||||
'/home/u',
|
||||
'ORCA-NATIVE-DEPS-OK',
|
||||
'DEAD',
|
||||
'READY'
|
||||
])
|
||||
|
||||
await deployAndLaunchRelay(conn)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,13 +11,18 @@ const { detectRemoteHostPlatform } = await import('./ssh-remote-platform-detecti
|
|||
|
||||
const conn = {} as SshConnection
|
||||
|
||||
function decodePowerShellCommand(command: string): string {
|
||||
const match = command.match(/-EncodedCommand\s+([A-Za-z0-9+/=]+)/)
|
||||
return match ? Buffer.from(match[1], 'base64').toString('utf16le') : ''
|
||||
}
|
||||
|
||||
describe('detectRemoteHostPlatform', () => {
|
||||
beforeEach(() => {
|
||||
execCommandMock.mockReset()
|
||||
})
|
||||
|
||||
it('detects POSIX hosts from uname output', async () => {
|
||||
execCommandMock.mockResolvedValueOnce('Linux x86_64\n')
|
||||
execCommandMock.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64\n')
|
||||
|
||||
await expect(detectRemoteHostPlatform(conn)).resolves.toMatchObject({
|
||||
relayPlatform: 'linux-x64',
|
||||
|
|
@ -25,13 +30,16 @@ describe('detectRemoteHostPlatform', () => {
|
|||
arch: 'x64',
|
||||
pathFlavor: 'posix'
|
||||
})
|
||||
expect(execCommandMock).toHaveBeenCalledWith(conn, 'uname -sm')
|
||||
expect(execCommandMock).toHaveBeenCalledWith(
|
||||
conn,
|
||||
"printf '\\n%s ' '__ORCA_REMOTE_PLATFORM__'; uname -sm"
|
||||
)
|
||||
})
|
||||
|
||||
it('falls back to PowerShell detection for Windows remotes', async () => {
|
||||
execCommandMock
|
||||
.mockRejectedValueOnce(new Error('uname unavailable'))
|
||||
.mockResolvedValueOnce('Windows AMD64\r\n')
|
||||
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Windows AMD64\r\n')
|
||||
|
||||
await expect(detectRemoteHostPlatform(conn)).resolves.toMatchObject({
|
||||
relayPlatform: 'win32-x64',
|
||||
|
|
@ -45,17 +53,50 @@ describe('detectRemoteHostPlatform', () => {
|
|||
expect.stringContaining('powershell.exe'),
|
||||
{ wrapCommand: false }
|
||||
)
|
||||
const command = execCommandMock.mock.calls[1]?.[1] ?? ''
|
||||
expect(decodePowerShellCommand(command)).toContain(
|
||||
'Write-Output ("`n__ORCA_REMOTE_PLATFORM__ Windows " + $arch)'
|
||||
)
|
||||
})
|
||||
|
||||
it('ignores untagged platforms before the tagged Windows ARM64 result', async () => {
|
||||
execCommandMock
|
||||
.mockRejectedValueOnce(new Error('uname unavailable'))
|
||||
.mockResolvedValueOnce(
|
||||
'Linux x86_64\r\nWindows AMD64\r\n#< CLIXML\r\n' +
|
||||
'__ORCA_REMOTE_PLATFORM__ Windows ARM64\r\n'
|
||||
)
|
||||
|
||||
await expect(detectRemoteHostPlatform(conn)).resolves.toMatchObject({
|
||||
relayPlatform: 'win32-arm64',
|
||||
os: 'win32',
|
||||
arch: 'arm64',
|
||||
pathFlavor: 'windows'
|
||||
})
|
||||
})
|
||||
|
||||
it('ignores a marker concatenated to unterminated startup noise', async () => {
|
||||
execCommandMock.mockResolvedValueOnce(
|
||||
'startup noise__ORCA_REMOTE_PLATFORM__ Linux x86_64\n' +
|
||||
'__ORCA_REMOTE_PLATFORM__ Linux arm64\n'
|
||||
)
|
||||
|
||||
await expect(detectRemoteHostPlatform(conn)).resolves.toMatchObject({
|
||||
relayPlatform: 'linux-arm64'
|
||||
})
|
||||
})
|
||||
|
||||
it('returns null when neither probe yields a supported platform', async () => {
|
||||
execCommandMock.mockResolvedValueOnce('Linux').mockResolvedValueOnce('FreeBSD x86_64')
|
||||
execCommandMock
|
||||
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux')
|
||||
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ FreeBSD x86_64')
|
||||
|
||||
await expect(detectRemoteHostPlatform(conn)).resolves.toBeNull()
|
||||
})
|
||||
|
||||
it('does not use whitespace regex splitting for remote platform output', async () => {
|
||||
const splitSpy = vi.spyOn(String.prototype, 'split')
|
||||
execCommandMock.mockResolvedValueOnce('Darwin arm64 extra')
|
||||
execCommandMock.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Darwin arm64 extra')
|
||||
|
||||
await expect(detectRemoteHostPlatform(conn)).resolves.toMatchObject({
|
||||
relayPlatform: 'darwin-arm64'
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
import type { SshConnection } from './ssh-connection'
|
||||
import { getProcessOutputFields } from '../../shared/process-output-field-scanner'
|
||||
import {
|
||||
getProcessOutputFields,
|
||||
iterateProcessOutputLines
|
||||
} from '../../shared/process-output-field-scanner'
|
||||
import { parseUnameToRelayPlatform, type RelayPlatform } from './relay-protocol'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { getRemoteHostPlatform, type RemoteHostPlatform } from './ssh-remote-platform'
|
||||
import { powerShellCommand } from './ssh-remote-powershell'
|
||||
|
||||
const PLATFORM_PROBE_MARKER = '__ORCA_REMOTE_PLATFORM__'
|
||||
|
||||
export async function detectRemoteHostPlatform(
|
||||
conn: SshConnection
|
||||
): Promise<RemoteHostPlatform | null> {
|
||||
|
|
@ -18,12 +23,12 @@ export async function detectRemoteHostPlatform(
|
|||
|
||||
async function detectUnamePlatform(conn: SshConnection): Promise<RelayPlatform | null> {
|
||||
try {
|
||||
const output = await execCommand(conn, 'uname -sm')
|
||||
const parts = getProcessOutputFields(output, 2)
|
||||
if (parts.length < 2) {
|
||||
return null
|
||||
}
|
||||
return parseUnameToRelayPlatform(parts[0], parts[1])
|
||||
const output = await execCommand(
|
||||
conn,
|
||||
// Why: Remote startup output may omit its trailing newline and must not absorb the marker.
|
||||
`printf '\\n%s ' '${PLATFORM_PROBE_MARKER}'; uname -sm`
|
||||
)
|
||||
return parseRemotePlatformOutput(output)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
|
|
@ -35,15 +40,27 @@ async function detectWindowsPlatform(conn: SshConnection): Promise<RelayPlatform
|
|||
'$arch = $env:PROCESSOR_ARCHITECTURE',
|
||||
'try { $runtimeArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString(); if ($runtimeArch) { $arch = $runtimeArch } } catch {}',
|
||||
'if (-not $arch) { $arch = $env:PROCESSOR_ARCHITECTURE }',
|
||||
'Write-Output ("Windows " + $arch)'
|
||||
// Why: Remote startup output may omit its trailing newline and must not absorb the marker.
|
||||
`Write-Output ("\`n${PLATFORM_PROBE_MARKER} Windows " + $arch)`
|
||||
].join('; ')
|
||||
const output = await execCommand(conn, powerShellCommand(script), { wrapCommand: false })
|
||||
const parts = getProcessOutputFields(output, 2)
|
||||
if (parts.length < 2 || parts[0].toLowerCase() !== 'windows') {
|
||||
return null
|
||||
}
|
||||
return parseUnameToRelayPlatform('Windows', parts[1])
|
||||
return parseRemotePlatformOutput(output)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function parseRemotePlatformOutput(output: string): RelayPlatform | null {
|
||||
// Why: SSH startup noise can resemble valid probe output and select the wrong relay.
|
||||
for (const line of iterateProcessOutputLines(output)) {
|
||||
const parts = getProcessOutputFields(line, 3)
|
||||
if (parts.length < 3 || parts[0] !== PLATFORM_PROBE_MARKER) {
|
||||
continue
|
||||
}
|
||||
const platform = parseUnameToRelayPlatform(parts[1], parts[2])
|
||||
if (platform) {
|
||||
return platform
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ describe('joinRemotePath', () => {
|
|||
|
||||
describe('detectRemoteHostPlatform', () => {
|
||||
it('uses uname when the remote is POSIX', async () => {
|
||||
vi.mocked(execCommand).mockResolvedValueOnce('Darwin arm64')
|
||||
vi.mocked(execCommand).mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Darwin arm64')
|
||||
|
||||
await expect(detectRemoteHostPlatform(conn)).resolves.toMatchObject({
|
||||
relayPlatform: 'darwin-arm64',
|
||||
|
|
@ -46,7 +46,7 @@ describe('detectRemoteHostPlatform', () => {
|
|||
it('falls back to PowerShell when uname is unavailable on Windows', async () => {
|
||||
vi.mocked(execCommand)
|
||||
.mockRejectedValueOnce(new Error('uname not recognized'))
|
||||
.mockResolvedValueOnce('Windows AMD64')
|
||||
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Windows AMD64')
|
||||
|
||||
await expect(detectRemoteHostPlatform(conn)).resolves.toMatchObject({
|
||||
relayPlatform: 'win32-x64',
|
||||
|
|
@ -59,5 +59,6 @@ describe('detectRemoteHostPlatform', () => {
|
|||
expect(script).toContain('$arch = $env:PROCESSOR_ARCHITECTURE')
|
||||
expect(script).toContain('try { $runtimeArch =')
|
||||
expect(script).toContain('catch {}')
|
||||
expect(script).toContain('Write-Output ("`n__ORCA_REMOTE_PLATFORM__ Windows " + $arch)')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue