refactor(ssh): remove the ssh-relay-deploy-helpers re-export module (#27961)

Co-authored-by: m4air <m4air@Mac.localdomain>
This commit is contained in:
OrcaWin
2026-10-12 03:59:05 -07:00
committed by GitHub
co-authored by m4air
parent 93e0b80be7
commit 799ec595ba
89 changed files with 521 additions and 423 deletions
+1 -1
View File
@@ -5462,7 +5462,7 @@
"src/main/ssh/ssh-reconnect-error-classification.test.ts": 123,
"src/main/ssh/ssh-reconnect-ladder.test.ts": 96,
"src/main/ssh/ssh-relay-answered-failure-producers.test.ts": 301,
"src/main/ssh/ssh-relay-deploy-helpers.test.ts": 571,
"src/main/ssh/ssh-relay-sentinel.test.ts": 571,
"src/main/ssh/ssh-relay-endpoint-incumbent-shell.integration.test.ts": 2228,
"src/main/ssh/ssh-relay-endpoint-incumbent.test.ts": 115,
"src/main/ssh/ssh-relay-endpoint-pty-count.test.ts": 695,
@@ -62,7 +62,7 @@ describe('a connect cancelled during its server decision', () => {
// The census dials after the teardown, opening a transport no one else holds.
recordSshConnectionOpened(asTransport(opened))
mockConnectionManager.getConnection.mockReturnValue(opened)
return { route: 'relay', reason: 'failed' }
return { route: 'unserved', reason: 'failed' }
})
await expect(
Promise.resolve(handlers.get('ssh:connect')!(null, { targetId: 'ssh-1' }))
@@ -90,7 +90,7 @@ describe('a connect cancelled during its server decision', () => {
recordSshConnectionOpened(asTransport(opened))
mockConnectionManager.getConnection.mockReturnValue(opened)
await new Promise<void>((resolve) => (resumeStale = resolve))
return { route: 'relay', reason: 'orcad_unavailable', detail: 'unsupported_host' }
return { route: 'unserved', reason: 'orcad_unavailable', detail: 'unsupported_host' }
})
const stale = Promise.resolve(handlers.get('ssh:connect')!(null, { targetId: 'ssh-1' }))
await vi.waitFor(() => expect(staleDecided).toBe(true))
+1 -1
View File
@@ -99,7 +99,7 @@ export function publishManagedServerConnect(
*/
export function publishUnservedHostServer(
target: SshTarget,
decision: Extract<HostServerOnConnectResult, { route: 'relay' }>
decision: Extract<HostServerOnConnectResult, { route: 'unserved' }>
): Error {
const managedServer = unservedServerStatus(decision)
setSshHostServerStatus(target.id, managedServer)
@@ -80,7 +80,11 @@ describe('connect-time server decision against the real profile', () => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
await expect(
resolveHostServerOnConnect(target(), hostServerOnConnectDeps(userDataPath))
).resolves.toEqual({ route: 'relay', reason: 'orcad_unavailable', detail: 'unsupported_host' })
).resolves.toEqual({
route: 'unserved',
reason: 'orcad_unavailable',
detail: 'unsupported_host'
})
warn.mockRestore()
expect(target().orcadFence).toBeUndefined()
expect(target().managedServerUnavailable).toEqual({
+1 -1
View File
@@ -70,7 +70,7 @@ describe('SSH IPC handlers', () => {
it('fails a host the managed server does not serve, with no fallback', async () => {
const decision = {
route: 'relay' as const,
route: 'unserved' as const,
reason: 'orcad_unavailable' as const,
detail: 'unsupported_host'
}
@@ -4,6 +4,7 @@
* are installed side by side as `~/.orca-remote/orcad-<version>/`, launched and stopped with
* the client's own deploy commands, and share one data root.
*/
import type * as ExecCommandModule from '../ssh/ssh-relay-exec-command'
import { build } from 'esbuild'
import {
copyFileSync,
@@ -59,7 +60,10 @@ import {
// GC runs its real commands, against this machine instead of an SSH host.
const { execMock } = vi.hoisted(() => ({ execMock: vi.fn() }))
vi.mock('../ssh/ssh-relay-deploy-helpers', () => ({ execCommand: execMock }))
vi.mock('../ssh/ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: execMock
}))
type Runtime = 'Bun' | 'Node'
type Slot = { runtime: Runtime; dir: string; version: string }
@@ -10,7 +10,7 @@ import type {
import { parseAppSshPtyId } from '../../../shared/ssh-pty-id'
import type { SshRemotePtyLease } from '../../../shared/ssh-types'
import type { SshConnection } from '../ssh-connection'
import { waitForSentinel } from '../ssh-relay-deploy-helpers'
import { waitForSentinel } from '../ssh-relay-sentinel'
import { probeRelayEndpointIncumbent } from '../ssh-relay-endpoint-incumbent'
import { isWindowsRemoteHost } from '../ssh-remote-platform'
import { LegacyRelayClient } from './legacy-relay-client'
@@ -1,3 +1,4 @@
import type * as ExecCommandModule from '../ssh-relay-exec-command'
import { describe, expect, it, vi } from 'vitest'
import type { SshConnection } from '../ssh-connection'
import { relaySocketNameForInstanceId } from '../ssh-relay-instance-id'
@@ -5,7 +6,10 @@ import { windowsRelayPipePathsForSocketName } from '../ssh-relay-endpoints'
import { getRemoteHostPlatform, joinRemotePath } from '../ssh-remote-platform'
const { execCommand } = vi.hoisted(() => ({ execCommand: vi.fn() }))
vi.mock('../ssh-relay-deploy-helpers', () => ({ execCommand }))
vi.mock('../ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand
}))
vi.mock('../ssh-remote-platform-detection', () => ({
detectRemoteHostPlatform: async () => getRemoteHostPlatform('win32-x64')
}))
@@ -9,7 +9,7 @@ import type { SshConnection } from '../ssh-connection'
import { shellEscape } from '../ssh-connection-utils'
import { RELAY_REMOTE_DIR } from '../relay-protocol'
import { SHORT_RELAY_SOCKET_DIR_PREFIX } from '../relay-socket-path-limit'
import { execCommand } from '../ssh-relay-deploy-helpers'
import { execCommand } from '../ssh-relay-exec-command'
import { readRelayDaemonRuntimes } from '../ssh-relay-endpoint-runtime'
import { relaySocketNameForInstanceId } from '../ssh-relay-instance-id'
import { readRemoteHomeCommand } from '../ssh-remote-commands'
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
import type * as ExecCommandModule from './ssh-relay-exec-command'
import type * as RecordFile from './orcad-remote-record-file'
import type * as InstallLock from './ssh-relay-install-lock'
import type * as VersionedInstall from './remote-versioned-install'
@@ -11,8 +11,8 @@ vi.mock('node:crypto', async (importOriginal) => {
return { ...actual, randomUUID: () => uuid.fixed ?? actual.randomUUID() }
})
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
...(await importOriginal<typeof DeployHelpers>()),
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
vi.mock('./ssh-connection-utils', () => ({
@@ -38,7 +38,7 @@ vi.mock('./orcad-local-build-hash', () => ({
computeLocalOrcadBuildHash: () => 'abc123def4567890'
}))
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { acquireInstallLock } from './ssh-relay-install-lock'
import { writeAtomicOrcadRemoteRecord } from './orcad-remote-record-file'
import { deployOrcad } from './orcad-remote-deploy'
@@ -11,14 +11,14 @@ import {
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
import type * as ExecCommandModule from './ssh-relay-exec-command'
// Every remote command runs in a real local shell, so the host-side checks are the real ones.
const shell = vi.hoisted((): { env: NodeJS.ProcessEnv | undefined } => ({ env: undefined }))
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => {
vi.mock('./ssh-relay-exec-command', async (importOriginal) => {
const { runProcess } = await import('@orca/process-host')
return {
...(await importOriginal<typeof DeployHelpers>()),
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: async (_conn: unknown, command: string) => {
const result = await runProcess({ program: '/bin/sh', args: ['-c', command], env: shell.env })
if (result.code !== 0) {
+1 -1
View File
@@ -12,7 +12,7 @@ import {
withoutAbortSignal,
type OrcadRemoteExecTarget
} from './orcad-remote-runtime-control'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { acquireInstallLock, RemoteInstallLockBusyError } from './ssh-relay-install-lock'
import {
orphanInstallLockCommand,
@@ -1,9 +1,9 @@
import { describe, expect, it, vi } from 'vitest'
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
import type * as ExecCommandModule from './ssh-relay-exec-command'
import type * as InstallLock from './ssh-relay-install-lock'
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
...(await importOriginal<typeof DeployHelpers>()),
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn().mockResolvedValue('')
}))
vi.mock('./ssh-relay-install-lock', async (importOriginal) => ({
@@ -11,7 +11,7 @@ vi.mock('./ssh-relay-install-lock', async (importOriginal) => ({
acquireInstallLock: vi.fn().mockResolvedValue(undefined)
}))
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { acquireInstallLock, RemoteInstallLockBusyError } from './ssh-relay-install-lock'
import {
parseOrcadActivationTransaction,
@@ -5,7 +5,7 @@ import {
launchOrcadAndAwaitReadiness,
type OrcadRemoteExecTarget
} from './orcad-remote-runtime-control'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
type CandidateLaunchTarget = Parameters<typeof launchOrcadAndAwaitReadiness>[0] &
OrcadRemoteExecTarget & {
+1 -1
View File
@@ -5,7 +5,7 @@
* lost answer is `unverifiable`, never exit; cancelling is how a stop that did not finish is
* withdrawn, and only an orcad-confirmed cancellation lets the fence go.
*/
import { isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import {
cancelRemoteOrcadManagedStop,
completeRemoteOrcadManagedStop
+6 -2
View File
@@ -1,3 +1,4 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
parseGlibcVersion,
@@ -6,10 +7,13 @@ import {
} from './orcad-deployment-target'
import { SshConnection } from './ssh-connection'
import { createCallbacks, createTarget } from './ssh-connection-test-fixtures'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { getRemoteHostPlatform } from './ssh-remote-platform'
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
async function resolveOrcadDeploymentTarget(
options: Parameters<typeof resolveOrcadDeploymentTargetFacts>[0]
+1 -1
View File
@@ -7,7 +7,7 @@ import {
} from '../../shared/node-runtime-pin'
import { ORCAD_SERVER_TARGET_FILENAME } from '../../shared/orcad-artifacts'
import type { RemoteInstallTransport } from './remote-install-transport'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import type { RemoteHostPlatform } from './ssh-remote-platform'
/** The host answered the libc probe, but with nothing this client recognises. */
@@ -11,13 +11,13 @@ import { hostname, tmpdir, uptime } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { runProcess, spawnProcess } from '@orca/process-host'
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
import type * as ExecCommandModule from './ssh-relay-exec-command'
// The proof's remote commands run in a real local shell.
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => {
vi.mock('./ssh-relay-exec-command', async (importOriginal) => {
const { runProcess: run } = await import('@orca/process-host')
return {
...(await importOriginal<typeof DeployHelpers>()),
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: async (_conn: unknown, command: string) =>
(await run({ program: '/bin/sh', args: ['-c', command] })).stdout
}
+3 -3
View File
@@ -11,15 +11,15 @@ import {
import { hostname, tmpdir, uptime } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
import type * as ExecCommandModule from './ssh-relay-exec-command'
// Every remote command runs in a real local shell, so the host-side checks are the real ones.
const shell = vi.hoisted((): { env: NodeJS.ProcessEnv | undefined } => ({ env: undefined }))
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => {
vi.mock('./ssh-relay-exec-command', async (importOriginal) => {
const { runProcess } = await import('@orca/process-host')
const { sshCommandExitError } = await import('./ssh-relay-exec-command')
return {
...(await importOriginal<typeof DeployHelpers>()),
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: async (_conn: unknown, command: string) => {
const result = await runProcess({ program: '/bin/sh', args: ['-c', command], env: shell.env })
if (result.code !== 0) {
@@ -11,14 +11,14 @@ import {
import { hostname, tmpdir, uptime } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
import type * as ExecCommandModule from './ssh-relay-exec-command'
// Every remote command runs in a real local shell, so the host-side checks are the real ones.
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => {
vi.mock('./ssh-relay-exec-command', async (importOriginal) => {
const { runProcess } = await import('@orca/process-host')
const { sshCommandExitError } = await import('./ssh-relay-exec-command')
return {
...(await importOriginal<typeof DeployHelpers>()),
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: async (_conn: unknown, command: string) => {
const result = await runProcess({ program: '/bin/sh', args: ['-c', command] })
if (result.code !== 0) {
@@ -2,15 +2,15 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { hostname, tmpdir, uptime } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
import type * as ExecCommandModule from './ssh-relay-exec-command'
// The host script's own checks are covered against the real script; here only the client's part.
const remote = vi.hoisted((): { commands: string[]; reply: string } => ({
commands: [],
reply: ''
}))
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
...(await importOriginal<typeof DeployHelpers>()),
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: async (_conn: unknown, command: string) => {
remote.commands.push(command)
return command.includes('script-present') ? 'ORCAD_HOST_SCRIPT_PRESENT\n' : remote.reply
+1 -1
View File
@@ -8,7 +8,7 @@
import { orcadRemoteBaseDir } from './orcad-remote-windows-node'
import { randomUUID } from 'node:crypto'
import type { OrcadDeployOptions, OrcadDeployResult } from './orcad-remote-deploy'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import {
withActivatedVersion,
type OrcadActivationRecord,
+1 -1
View File
@@ -16,7 +16,7 @@ import { execOrcadRemote } from './orcad-remote-runtime-control'
import { readBoundedOrcadRemoteRecord } from './orcad-remote-record-file'
import { orcadSlotDir, type OrcadSlotOptions } from './orcad-recovery-slot'
import { isWindowsRemoteHost, joinRemotePath } from './ssh-remote-platform'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import {
orcadWindowsNodeCommandLine,
orcadWindowsSlotOpCommand,
+4 -4
View File
@@ -1,9 +1,9 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
import type * as ExecCommandModule from './ssh-relay-exec-command'
import type * as InstallLock from './ssh-relay-install-lock'
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
...(await importOriginal<typeof DeployHelpers>()),
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
vi.mock('./ssh-connection-utils', () => ({ shellEscape: (s: string) => `'${s}'` }))
@@ -12,7 +12,7 @@ vi.mock('./ssh-relay-install-lock', async (importOriginal) => ({
acquireInstallLock: vi.fn()
}))
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { acquireInstallLock, RemoteInstallLockBusyError } from './ssh-relay-install-lock'
import { wakeStoppedManagedOrcad } from './orcad-managed-wake'
import { getRemoteHostPlatform } from './ssh-remote-platform'
@@ -1,6 +1,7 @@
// Astra pass 4 §2: desktop A leases nothing, but desktop B's live shell, under another target id on
// the same account, must never read as exited. COMPAT(legacy-relay-bridge): the host converts anyway
// and leaves B's relay alone, so the census reads `bridged` with nothing of A's to serve.
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { SshConnection } from './ssh-connection'
import { relaySocketNameForInstanceId } from './ssh-relay-instance-id'
@@ -19,7 +20,10 @@ const {
countRelayPtysOverBridge: vi.fn()
}))
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand }))
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand
}))
vi.mock('./ssh-relay-endpoint-incumbent', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
probeRelayEndpointIncumbent
@@ -1,10 +1,10 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
import type * as ExecCommandModule from './ssh-relay-exec-command'
import type * as RecordFile from './orcad-remote-record-file'
import type * as InstallLock from './ssh-relay-install-lock'
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
...(await importOriginal<typeof DeployHelpers>()),
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
vi.mock('./ssh-connection-utils', () => ({ shellEscape: (s: string) => `'${s}'` }))
@@ -17,7 +17,7 @@ vi.mock('./orcad-remote-record-file', async (importOriginal) => ({
writeAtomicOrcadRemoteRecord: vi.fn()
}))
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { acquireInstallLock } from './ssh-relay-install-lock'
import { writeAtomicOrcadRemoteRecord } from './orcad-remote-record-file'
import { decommissionRemoteOrcad } from './orcad-remote-decommission'
+5 -4
View File
@@ -1,3 +1,4 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { chmodSync, mkdirSync, mkdtempSync, rmSync, statSync, writeFileSync } from 'node:fs'
import type * as RecordFile from './orcad-remote-record-file'
import { tmpdir } from 'node:os'
@@ -5,9 +6,9 @@ import { dirname, join } from 'node:path'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { runProcess } from '@orca/process-host'
vi.mock('./ssh-relay-deploy-helpers', () => ({
execCommand: vi.fn(),
isUnconfirmedSshCommandTermination: () => false
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
vi.mock('./ssh-connection-utils', () => ({ shellEscape: (s: string) => `'${s}'` }))
vi.mock('./ssh-relay-install-lock', () => ({
@@ -31,7 +32,7 @@ vi.mock('./orcad-local-build-hash', () => ({
computeLocalOrcadBuildHash: () => 'abc123def4567890'
}))
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { acquireInstallLock, isRelayInstallLockStale } from './ssh-relay-install-lock'
import { uploadRelayDirectory } from './ssh-relay-install-transfers'
import { writeAtomicOrcadRemoteRecord } from './orcad-remote-record-file'
+5 -4
View File
@@ -1,8 +1,9 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('./ssh-relay-deploy-helpers', () => ({
execCommand: vi.fn(),
isUnconfirmedSshCommandTermination: () => false
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
vi.mock('./ssh-connection-utils', () => ({ shellEscape: (s: string) => `'${s}'` }))
vi.mock('./ssh-relay-gc-claim', () => ({
@@ -20,7 +21,7 @@ vi.mock('./ssh-relay-install-lock', () => ({
isRelayInstallLockStale: vi.fn().mockResolvedValue(false)
}))
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { gcOldOrcadVersions } from './orcad-remote-gc'
import { emptyOrcadActivationRecord } from './orcad-activation-record'
import { serializeOrcadActivationTransaction } from './orcad-activation-transaction'
+1 -2
View File
@@ -14,8 +14,7 @@
* pinned too, and an unreadable journal skips the pass entirely.
*/
import type { OrcadHostTransport } from './orcad-host-transport'
import { execCommand } from './ssh-relay-deploy-helpers'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { ORCAD_INSTALL_MODEL } from './remote-install-model'
import { gcOldRemoteInstallVersions } from './remote-install-gc'
import { orcadGcPinnedDirNames, type OrcadActivationRecord } from './orcad-activation-record'
@@ -1,6 +1,10 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
vi.mock('./ssh-relay-install-lock', () => ({
acquireInstallLock: vi.fn().mockResolvedValue(undefined),
RELAY_INSTALL_LOCK_NAME: '.install-lock'
@@ -14,7 +18,7 @@ vi.mock('./orcad-remote-node-runtime', () => ({
}))
import type { OrcadHostTransport } from './orcad-host-transport'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { uploadRelayDirectory, writeRelayFile } from './ssh-relay-install-transfers'
import { installOrcadBundle } from './orcad-remote-install'
import { getRemoteHostPlatform } from './ssh-remote-platform'
+1 -2
View File
@@ -3,8 +3,7 @@ import { orcadNodePtyNativeArtifacts, orcadRipgrepArtifact } from '../../shared/
import { orcadAgentBrowserNativeName } from '../../shared/orcad-agent-browser-name'
import type { NodeRuntimeTarget } from '../../shared/node-runtime-pin'
import { ensureRemoteOrcadNodeRuntime } from './orcad-remote-node-runtime'
import { execCommand } from './ssh-relay-deploy-helpers'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { shellEscape } from './ssh-connection-utils'
import type { OrcadHostTransport } from './orcad-host-transport'
import { joinRemotePath, type RemoteHostPlatform } from './ssh-remote-platform'
@@ -1,3 +1,4 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import type * as TerminalBarrier from './orcad-rollback-terminal-barrier'
/**
* The deploy and rollback drivers end to end against a Windows host whose every host op is answered by a
@@ -6,9 +7,9 @@ import type * as TerminalBarrier from './orcad-rollback-terminal-barrier'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type * as RecordFile from './orcad-remote-record-file'
vi.mock('./ssh-relay-deploy-helpers', () => ({
execCommand: vi.fn(),
isUnconfirmedSshCommandTermination: () => false
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
vi.mock('./ssh-relay-install-lock', () => ({
acquireInstallLock: vi.fn().mockResolvedValue(undefined),
@@ -49,7 +50,7 @@ vi.mock('./remote-versioned-install', async (importOriginal) => ({
abandonInstall: vi.fn().mockResolvedValue(undefined)
}))
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { deployOrcad, type OrcadDeployOptions } from './orcad-remote-deploy'
import { rollbackOrcad } from './orcad-remote-rollback'
import { emptyOrcadActivationRecord } from './orcad-activation-record'
+5 -1
View File
@@ -1,3 +1,4 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
@@ -14,7 +15,10 @@ import { ORCAD_WINDOWS_HOST_SCRIPT } from './orcad-windows-host-script'
import { getRemoteHostPlatform } from './ssh-remote-platform'
const execCommand = vi.hoisted(() => vi.fn())
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand }))
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand
}))
const linux = getRemoteHostPlatform('linux-x64')
const windows = getRemoteHostPlatform('win32-x64')
+1 -1
View File
@@ -6,7 +6,7 @@
import { redactString } from '../observability/redactor'
import { shellEscape } from './ssh-connection-utils'
import { ORCAD_LOG_FILENAME } from './orcad-remote-host-support'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { isWindowsRemoteHost, joinRemotePath, type RemoteHostPlatform } from './ssh-remote-platform'
import {
orcadWindowsSlotOpCommand,
@@ -1,3 +1,4 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
@@ -10,10 +11,13 @@ import {
REMOTE_NODE_RUNTIME_READY
} from './orcad-remote-node-runtime'
import { RUNTIME_STORE_LOCK_NAME } from './remote-node-runtime-store-lock'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { getRemoteHostPlatform } from './ssh-remote-platform'
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
vi.mock('./ssh-relay-install-transfers', () => ({ uploadRelayDirectory: vi.fn(async () => {}) }))
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: every remote call goes through the mocked execCommand.
@@ -1,3 +1,4 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
@@ -13,11 +14,14 @@ import {
} from './orcad-remote-node-runtime'
import { REMOTE_NODE_RUNTIME_VERIFIED_MARKER } from './orcad-remote-node-runtime-report'
import { RUNTIME_STORE_LOCK_NAME, withRuntimeStoreLock } from './remote-node-runtime-store-lock'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { uploadRelayDirectory } from './ssh-relay-install-transfers'
import { getRemoteHostPlatform } from './ssh-remote-platform'
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
vi.mock('./ssh-relay-install-transfers', () => ({ uploadRelayDirectory: vi.fn() }))
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: every remote call goes through the mocked execCommand, which runs it in a local shell.
@@ -1,8 +1,9 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import path from 'node:path'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { NODE_RUNTIME_ASSETS, NODE_RUNTIME_PIN } from '../../shared/node-runtime-pin'
import type { OrcadHostTransport } from './orcad-host-transport'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { uploadRelayDirectory } from './ssh-relay-install-transfers'
import {
ensureRemoteOrcadNodeRuntime,
@@ -22,7 +23,10 @@ import {
import { getRemoteHostPlatform } from './ssh-remote-platform'
import { decodeRemotePowerShellScript } from './ssh-remote-powershell'
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
vi.mock('./ssh-relay-install-transfers', () => ({ uploadRelayDirectory: vi.fn() }))
const host = getRemoteHostPlatform('win32-x64')
+1 -2
View File
@@ -24,8 +24,7 @@ import {
} from '../../shared/orcad-artifacts'
import type { OrcadHostTransport } from './orcad-host-transport'
import { shellEscape } from './ssh-connection-utils'
import { execCommand } from './ssh-relay-deploy-helpers'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { uploadRelayDirectory } from './ssh-relay-install-transfers'
import {
isWindowsRemoteHost,
+1 -1
View File
@@ -15,7 +15,7 @@ import {
import { readWindowsOrcadSlotEntry } from './orcad-remote-launch-windows'
import { ORCAD_WINDOWS_RUNTIME_MARKER } from './orcad-windows-host-script'
import { orcadNodeSlotRuntimeCommand, selectOrcadSlotEntryCommand } from './orcad-remote-runtime'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { shellEscape } from './ssh-connection-utils'
import { isWindowsRemoteHost, joinRemotePath, type RemoteHostPlatform } from './ssh-remote-platform'
import type { OrcadHostTransport } from './orcad-host-transport'
+4 -2
View File
@@ -1,6 +1,8 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('./ssh-relay-deploy-helpers', () => ({
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn(),
isUnconfirmedSshCommandTermination: (error: unknown) =>
error instanceof Error && error.message === 'channel lost'
@@ -9,7 +11,7 @@ vi.mock('./ssh-relay-deploy-helpers', () => ({
vi.mock('./ssh-remote-platform-detection', () => ({ detectRemoteHostPlatform: vi.fn() }))
vi.mock('./orcad-windows-host-preparation', () => ({ prepareWindowsOrcadHost: vi.fn() }))
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { detectRemoteHostPlatform } from './ssh-remote-platform-detection'
import { prepareWindowsOrcadHost } from './orcad-windows-host-preparation'
import { resolveOrcadRemoteContext } from './orcad-remote-context'
+1 -1
View File
@@ -8,7 +8,7 @@ import { randomUUID } from 'node:crypto'
import { shellEscape } from './ssh-connection-utils'
import { removeRemoteFileCommand } from './ssh-remote-commands'
import { execOrcadRemote, type OrcadRemoteExecTarget } from './orcad-remote-runtime-control'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { isWindowsRemoteHost } from './ssh-remote-platform'
import {
orcadRemoteBaseDir,
+5 -4
View File
@@ -1,11 +1,12 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type * as RecordFile from './orcad-remote-record-file'
import type * as InstallLock from './ssh-relay-install-lock'
import type * as TerminalBarrier from './orcad-rollback-terminal-barrier'
vi.mock('./ssh-relay-deploy-helpers', () => ({
execCommand: vi.fn(),
isUnconfirmedSshCommandTermination: () => false
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
vi.mock('./ssh-connection-utils', () => ({ shellEscape: (s: string) => `'${s}'` }))
vi.mock('./ssh-relay-install-transfers', () => ({
@@ -51,7 +52,7 @@ vi.mock('./orcad-rollback-terminal-barrier', async (importOriginal) => ({
}
}))
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { writeAtomicOrcadRemoteRecord } from './orcad-remote-record-file'
import { rollbackOrcad, type OrcadRollbackOptions } from './orcad-remote-rollback'
import { emptyOrcadActivationRecord, type OrcadActivationRecord } from './orcad-activation-record'
@@ -1,12 +1,14 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('./ssh-relay-deploy-helpers', () => ({
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn(),
isUnconfirmedSshCommandTermination: (error: unknown) =>
error instanceof Error && error.message === 'unconfirmed'
}))
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { launchOrcadAndAwaitReadiness } from './orcad-remote-runtime-control'
import { getRemoteHostPlatform } from './ssh-remote-platform'
import type { OrcadLaunchSpec } from './orcad-remote-launch'
+1 -1
View File
@@ -20,7 +20,7 @@ import {
} from './orcad-remote-readiness-wait'
import type { OrcadHostTransport } from './orcad-host-transport'
import { errorMessage } from '../../shared/error-message'
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import {
currentOrcadFence,
isOrcadFenceLost,
@@ -1,11 +1,12 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('./ssh-relay-deploy-helpers', () => ({
execCommand: vi.fn(),
isUnconfirmedSshCommandTermination: () => false
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import type { OrcadHostTransport } from './orcad-host-transport'
import { getRemoteHostPlatform } from './ssh-remote-platform'
import { orcadLivenessProbeCommand, type OrcadLaunchSpec } from './orcad-remote-launch'
@@ -3,13 +3,14 @@
* a staged managed request run by the slot's node.exe, and GC screens every candidate in one
* node.exe instead of one per version dir.
*/
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type * as RecordFile from './orcad-remote-record-file'
import type * as InstallLock from './ssh-relay-install-lock'
vi.mock('./ssh-relay-deploy-helpers', () => ({
execCommand: vi.fn(),
isUnconfirmedSshCommandTermination: () => false
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
vi.mock('./ssh-relay-install-lock', async (importOriginal) => ({
...(await importOriginal<typeof InstallLock>()),
@@ -24,7 +25,7 @@ vi.mock('./remote-install-gc', async (importOriginal) => ({
gcOldRemoteInstallVersions: vi.fn().mockResolvedValue(undefined)
}))
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { writeAtomicOrcadRemoteRecord } from './orcad-remote-record-file'
import { gcOldRemoteInstallVersions } from './remote-install-gc'
import { decommissionRemoteOrcad } from './orcad-remote-decommission'
+1 -1
View File
@@ -19,7 +19,7 @@ import {
import { pinnedNodeRuntimeAsset, type NodeRuntimeTarget } from '../../shared/node-runtime-pin'
import { randomUUID } from 'node:crypto'
import type { OrcadHostTransport } from './orcad-host-transport'
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { RELAY_REMOTE_DIR } from './relay-protocol'
import { joinRemotePath, remoteDirname, type RemoteHostPlatform } from './ssh-remote-platform'
import { powerShellLiteral } from './ssh-remote-powershell'
+1 -1
View File
@@ -2,7 +2,7 @@
import { orcadRemoteBaseDir } from './orcad-remote-windows-node'
import { randomUUID } from 'node:crypto'
import type { OrcadRollbackOptions, OrcadRollbackResult } from './orcad-remote-rollback'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { withRolledBackVersion } from './orcad-activation-record'
import { writeOrcadActivationRecord } from './orcad-activation-record-store'
import { launchAndJudgeOrcadSlot } from './orcad-candidate-launch-verdict'
+6 -2
View File
@@ -1,11 +1,15 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { resolveOrcadRuntimeTarget } from './orcad-runtime-target'
import { SshConnection } from './ssh-connection'
import { createCallbacks, createTarget } from './ssh-connection-test-fixtures'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { getRemoteHostPlatform } from './ssh-remote-platform'
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
beforeEach(() => vi.mocked(execCommand).mockReset())
function resolveOn(platform: Parameters<typeof getRemoteHostPlatform>[0], ldd: string) {
+1 -1
View File
@@ -12,7 +12,7 @@ import {
ORCAD_STATE_MUTATION_DEADLINE
} from './orcad-state-snapshot-members'
import type { OrcadRemoteExecTarget } from './orcad-remote-runtime-control'
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { isOrcadFenceLost, OrcadFenceLostError } from './orcad-activation-fence-scope'
// Longer than the host's own deadline, so the host kills the work before the client gives up.
@@ -5,13 +5,13 @@
* command shape) runs beside it, as the two restores did on a real host.
*/
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
import type * as ExecCommandModule from './ssh-relay-exec-command'
import type * as RecordFile from './orcad-remote-record-file'
import type * as InstallLock from './ssh-relay-install-lock'
import type * as VersionedInstall from './remote-versioned-install'
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
...(await importOriginal<typeof DeployHelpers>()),
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
vi.mock('./ssh-connection-utils', () => ({ shellEscape: (s: string) => `'${s}'` }))
@@ -33,14 +33,17 @@ vi.mock('./orcad-local-build-hash', () => ({
computeLocalOrcadBuildHash: () => 'abc123def4567890'
}))
import { execCommand } from './ssh-relay-deploy-helpers'
import {
execCommand,
SSH_EXEC_TIMEOUT_CODE,
isUnconfirmedSshCommandTermination
} from './ssh-relay-exec-command'
import { acquireInstallLock } from './ssh-relay-install-lock'
import { writeAtomicOrcadRemoteRecord } from './orcad-remote-record-file'
import { deployOrcad } from './orcad-remote-deploy'
import { rollbackOrcad } from './orcad-remote-rollback'
import { recoverInterruptedOrcadActivation } from './orcad-activation-recovery'
import { getRemoteHostPlatform } from './ssh-remote-platform'
import { SSH_EXEC_TIMEOUT_CODE, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import type { OrcadHostTransport } from './orcad-host-transport'
import { BUILD_HASH, FakeOrcadHost, NEW, OLD } from './orcad-activation-host-test-harness'
import { isSnapshotCaptureCommand } from './orcad-snapshot-capture-command'
@@ -1,15 +1,15 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
import type * as ExecCommandModule from './ssh-relay-exec-command'
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
...(await importOriginal<typeof DeployHelpers>()),
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
import type { SshConnection } from './ssh-connection'
import { gcOldRemoteInstallVersions } from './remote-install-gc'
import { ORCAD_INSTALL_MODEL } from './remote-install-model'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { getRemoteHostPlatform } from './ssh-remote-platform'
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: all connection access is replaced by execCommand's mock.
+1 -2
View File
@@ -1,6 +1,5 @@
import type { RemoteInstallTransport } from './remote-install-transport'
import { execCommand } from './ssh-relay-deploy-helpers'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { remoteInstallGcTombstoneRegex, type RemoteInstallModel } from './remote-install-model'
import { removeRemoteTreeCommand } from './ssh-remote-commands'
import {
@@ -13,10 +13,10 @@ import { once } from 'node:events'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
import type * as ExecCommandModule from './ssh-relay-exec-command'
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
...(await importOriginal<typeof DeployHelpers>()),
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
@@ -34,7 +34,7 @@ import {
runtimeStoreInventoryCommand,
type RuntimeStoreInventory
} from './remote-node-runtime-store-inventory'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { getRemoteHostPlatform } from './ssh-remote-platform'
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: all connection access is replaced by execCommand's mock.
+1 -2
View File
@@ -28,9 +28,8 @@ import {
tryWithRuntimeStoreLock
} from './remote-node-runtime-store-lock'
import { shellEscape } from './ssh-connection-utils'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { INSTALL_LOCK_STALE_MS } from './ssh-relay-install-lock'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import {
moveRemoteTreeCommand,
removeRemoteTreeCommand,
@@ -1,3 +1,4 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
@@ -9,11 +10,14 @@ import {
RUNTIME_STORE_LOCK_OWNER_FILENAME,
withRuntimeStoreLock
} from './remote-node-runtime-store-lock'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { tryCreateInstallLockCommand } from './ssh-relay-install-lock-commands'
import { getRemoteHostPlatform } from './ssh-remote-platform'
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: every remote call goes through the mocked execCommand, which runs it in a local shell.
const conn = {} as SshConnection
@@ -3,10 +3,10 @@ import { copyFileSync, mkdirSync, mkdtempSync, utimesSync, writeFileSync } from
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
import type * as ExecCommandModule from './ssh-relay-exec-command'
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
...(await importOriginal<typeof DeployHelpers>()),
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
@@ -18,7 +18,7 @@ import {
windowsRuntimeStoreInventoryCommand,
windowsSweepStaleRuntimeStagesCommand
} from './remote-node-runtime-store-windows'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { getRemoteHostPlatform } from './ssh-remote-platform'
import { decodeRemotePowerShellScript } from './ssh-remote-powershell'
import { removeTreeSync } from '../../shared/windows-transient-lock-removal'
@@ -1,13 +1,13 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
import type * as ExecCommandModule from './ssh-relay-exec-command'
vi.mock('fs', () => ({
existsSync: vi.fn(),
readFileSync: vi.fn()
}))
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
...(await importOriginal<typeof DeployHelpers>()),
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
@@ -24,7 +24,7 @@ import {
} from './remote-versioned-install'
import { gcOldRemoteInstallVersions } from './remote-install-gc'
import { ORCAD_INSTALL_MODEL } from './remote-install-model'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { isUnconfirmedSshCommandTermination, execCommand } from './ssh-relay-exec-command'
import { acquireInstallLock } from './ssh-relay-install-lock'
import {
isRelayGcClaimed,
@@ -33,7 +33,6 @@ import {
tryAcquireRelayGcClaim,
waitForRelayGcClaimRelease
} from './ssh-relay-gc-claim'
import { execCommand } from './ssh-relay-deploy-helpers'
import { getRemoteHostPlatform, type RemoteHostPlatform } from './ssh-remote-platform'
import type { SshConnection } from './ssh-connection'
@@ -42,7 +42,7 @@ export async function uploadSshDirectory(
): Promise<void> {
await withLinkedSftpTransfer(host, remoteDir, options, {
sftp: async (sftp, targetDir, signal) => {
const { uploadDirectory } = await import('./ssh-relay-deploy-helpers')
const { uploadDirectory } = await import('./sftp-upload')
await uploadDirectory(sftp, localDir, targetDir, localDir, { signal })
},
system: (signal) =>
@@ -1,3 +1,4 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { SshConnection } from './ssh-connection'
import {
@@ -12,7 +13,10 @@ const { execCommand, probeRelayEndpointIncumbent, countRelayEndpointPtys } = vi.
countRelayEndpointPtys: vi.fn()
}))
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand }))
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand
}))
vi.mock('./ssh-relay-endpoint-pty-count', () => ({ countRelayEndpointPtys }))
vi.mock('./ssh-relay-endpoint-incumbent', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
@@ -10,7 +10,7 @@ import type { SshConnection } from './ssh-connection'
import { shellEscape } from './ssh-connection-utils'
import { RELAY_REMOTE_DIR } from './relay-protocol'
import { SHORT_RELAY_SOCKET_DIR_PREFIX } from './relay-socket-path-limit'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import {
isReapableRelayHusk,
probeRelayEndpointIncumbent,
@@ -10,7 +10,7 @@ import {
} from './ssh-host-relay-endpoint-census'
import type { SshConnection } from './ssh-connection'
import { censusWindowsHostRelays, windowsCensusNodePath } from './ssh-host-relay-windows-census'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { readRemoteHomeCommand } from './ssh-remote-commands'
import { resolveRemoteNodePath } from './ssh-remote-node-resolution'
import { detectRemoteHostPlatform } from './ssh-remote-platform-detection'
@@ -1,3 +1,4 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { SshConnection } from './ssh-connection'
import { relaySocketNameForInstanceId } from './ssh-relay-instance-id'
@@ -9,7 +10,10 @@ const { execCommand, countRelayPtysOverBridge } = vi.hoisted(() => ({
countRelayPtysOverBridge: vi.fn()
}))
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand }))
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand
}))
vi.mock('./ssh-relay-endpoint-pty-count', () => ({ countRelayPtysOverBridge }))
vi.mock('./ssh-remote-commands', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
@@ -19,7 +19,7 @@ import {
windowsRelayInventoryCommand
} from './ssh-host-relay-windows-inventory'
import { countRelayPtysOverBridge } from './ssh-relay-endpoint-pty-count'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { relaySocketNameForInstanceId } from './ssh-relay-instance-id'
import { windowsRelayConnectCommand } from './ssh-relay-windows-launch-command'
import { listRemoteInstallBaseDirsCommand } from './ssh-remote-commands'
@@ -155,7 +155,7 @@ describe('connect-decision telemetry', () => {
})
})
await expect(resolveHostServerOnConnect(target, d)).resolves.toEqual({
route: 'relay',
route: 'unserved',
reason: 'deferred',
detail: reason
})
+11 -11
View File
@@ -67,7 +67,7 @@ describe('which server an SSH host runs on connect', () => {
for (const verdict of ['live', 'unverifiable'] as const) {
const d = deps({ relayTerminals: async () => ({ verdict, count: 2 }) })
await expect(resolveHostServerOnConnect(target, d)).resolves.toEqual({
route: 'relay',
route: 'unserved',
reason: verdict === 'live' ? 'relay_terminals_live' : 'relay_terminals_unverifiable',
terminals: 2
})
@@ -77,7 +77,7 @@ describe('which server an SSH host runs on connect', () => {
relayTerminals: async () => ({ verdict: 'live', count: 1, elsewhere: true })
})
await expect(resolveHostServerOnConnect(target, elsewhere)).resolves.toEqual({
route: 'relay',
route: 'unserved',
reason: 'relay_terminals_live',
terminals: 1,
terminalsElsewhere: true
@@ -91,7 +91,7 @@ describe('which server an SSH host runs on connect', () => {
})
})
await expect(resolveHostServerOnConnect(target, raced)).resolves.toEqual({
route: 'relay',
route: 'unserved',
reason: 'relay_terminals_live'
})
})
@@ -118,7 +118,7 @@ describe('which server an SSH host runs on connect', () => {
})
})
await expect(resolveHostServerOnConnect(target, d)).resolves.toEqual({
route: 'relay',
route: 'unserved',
reason: 'refused',
detail: 'An automation still runs on this host.'
})
@@ -132,7 +132,7 @@ describe('which server an SSH host runs on connect', () => {
}
})
await expect(resolveHostServerOnConnect(target, d)).resolves.toEqual({
route: 'relay',
route: 'unserved',
reason: 'orcad_unavailable',
detail: 'unsupported_host'
})
@@ -173,7 +173,7 @@ describe('which server an SSH host runs on connect', () => {
},
changed
)
).resolves.toEqual({ route: 'relay', reason: 'source_changed' })
).resolves.toEqual({ route: 'unserved', reason: 'source_changed' })
expect(changed.ensureTunnel).not.toHaveBeenCalled()
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
@@ -194,7 +194,7 @@ describe('which server an SSH host runs on connect', () => {
it('touches no host when this build carries no orcad template', async () => {
const d = deps({ hasTemplate: () => false, isEmptyHost: () => true })
await expect(resolveHostServerOnConnect(target, d)).resolves.toEqual({
route: 'relay',
route: 'unserved',
reason: 'orcad_unavailable',
detail: 'artifacts_unavailable'
})
@@ -209,7 +209,7 @@ describe('which server an SSH host runs on connect', () => {
}
})
await expect(resolveHostServerOnConnect(target, d)).resolves.toEqual({
route: 'relay',
route: 'unserved',
reason: 'failed',
detail: 'Connection lost'
})
@@ -226,7 +226,7 @@ describe('which server an SSH host runs on connect', () => {
})
})
await expect(resolveHostServerOnConnect(target, d)).resolves.toEqual({
route: 'relay',
route: 'unserved',
reason: 'orcad_unavailable',
detail: 'ssh_tunnel_unavailable'
})
@@ -244,7 +244,7 @@ describe('which server an SSH host runs on connect', () => {
})
})
await expect(resolveHostServerOnConnect(target, d)).resolves.toEqual({
route: 'relay',
route: 'unserved',
reason: 'orcad_unavailable',
detail: 'ssh_tunnel_unavailable'
})
@@ -285,7 +285,7 @@ describe('which server an SSH host runs on connect', () => {
})
})
await expect(resolveHostServerOnConnect(target, d)).resolves.toMatchObject({
route: 'relay',
route: 'unserved',
reason: 'failed',
detail: 'orcad_migration_source_changed'
})
+10 -10
View File
@@ -52,7 +52,7 @@ export type HostServerOnConnectResult =
fenceHeld?: true
}
| {
route: 'relay'
route: 'unserved'
reason: SshManagedServerRelayReason
/** A blocker the user must act on, or why orcad can't run on this host. */
detail?: string
@@ -129,7 +129,7 @@ async function decide(
trace: HostServerDecisionTrace
): Promise<HostServerOnConnectResult> {
if (target.orcadFence?.sourceChangedAt) {
return { route: 'relay', reason: 'source_changed' }
return { route: 'unserved', reason: 'source_changed' }
}
const existing = deps.managedEnvironmentId(target)
if (existing) {
@@ -146,7 +146,7 @@ async function decide(
await deps.releaseUnreachableSetup(target)
deps.recordUnavailable(target, ORCAD_TUNNEL_UNAVAILABLE_REASON)
return {
route: 'relay',
route: 'unserved',
reason: 'orcad_unavailable',
detail: ORCAD_TUNNEL_UNAVAILABLE_REASON
}
@@ -177,10 +177,10 @@ async function decide(
const recorded = deps.recordedUnavailable(target)
if (recorded) {
trace.recorded = true
return { route: 'relay', reason: 'orcad_unavailable', detail: recorded }
return { route: 'unserved', reason: 'orcad_unavailable', detail: recorded }
}
if (!deps.hasTemplate()) {
return { route: 'relay', reason: 'orcad_unavailable', detail: 'artifacts_unavailable' }
return { route: 'unserved', reason: 'orcad_unavailable', detail: 'artifacts_unavailable' }
}
if (deps.isEmptyHost(target)) {
try {
@@ -207,7 +207,7 @@ async function convertHost(
if (terminals && terminals.verdict !== 'exited' && !terminals.bridged) {
// Why unverifiable too: loss of contact is never evidence that a relay terminal exited.
return {
route: 'relay',
route: 'unserved',
reason: terminalReason(terminals.verdict),
terminals: terminals.count,
...(terminals.elsewhere ? { terminalsElsewhere: true } : {})
@@ -269,9 +269,9 @@ async function afterConversion(
return deferred(target, deps, result, false)
case 'refused':
if (result.code === 'orcad_migration_terminals') {
return { route: 'relay', reason: terminalReason(result.verdict) }
return { route: 'unserved', reason: terminalReason(result.verdict) }
}
return { route: 'relay', reason: 'refused', detail: result.reason }
return { route: 'unserved', reason: 'refused', detail: result.reason }
}
}
@@ -306,10 +306,10 @@ async function unfinished(
}
if (!unavailable) {
// Shown under the SSH host, with the host's orcad.log tail when the failure carries one.
return { route: 'relay', reason, detail: boundedDetail(failure) }
return { route: 'unserved', reason, detail: boundedDetail(failure) }
}
deps.recordUnavailable(target, unavailable)
return { route: 'relay', reason: 'orcad_unavailable', detail: unavailable }
return { route: 'unserved', reason: 'orcad_unavailable', detail: unavailable }
}
function boundedDetail(text: string): string {
+1 -1
View File
@@ -2,7 +2,7 @@
import type { SshManagedServerStatus } from '../../shared/ssh-types'
import type { HostServerOnConnectResult } from './ssh-host-server-on-connect'
type UnservedDecision = Extract<HostServerOnConnectResult, { route: 'relay' }>
type UnservedDecision = Extract<HostServerOnConnectResult, { route: 'unserved' }>
// Why `kind: 'relay'`: the published status keeps its shipped shape; it now means "not served".
export function unservedServerStatus(
@@ -1,8 +1,10 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { describe, expect, it, vi } from 'vitest'
import { RELAY_LSOF_PROBE_JS } from '@orca/process-host/posix-lsof-probe'
const execCommand = vi.fn()
vi.mock('./ssh-relay-deploy-helpers', () => ({
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: (...args: unknown[]) => execCommand(...args),
isUnconfirmedSshCommandTermination: (error: unknown) =>
(error as { sshChannelCloseConfirmed?: boolean } | null)?.sshChannelCloseConfirmed === false
+1 -1
View File
@@ -26,7 +26,7 @@ import {
RELAY_UNRECOGNIZED_CHILD_COUNT_VAR,
relayDaemonChildCensusShell
} from './relay-daemon-service-children'
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { isWindowsRemoteHost, type RemoteHostPlatform } from './ssh-remote-platform'
type RelayEndpointVerdict = 'live' | 'unverifiable' | 'exited'
@@ -7,7 +7,7 @@ const { waitForSentinel, request, dispose } = vi.hoisted(() => ({
dispose: vi.fn()
}))
vi.mock('./ssh-relay-deploy-helpers', () => ({ waitForSentinel }))
vi.mock('./ssh-relay-sentinel', () => ({ waitForSentinel }))
vi.mock('./ssh-channel-multiplexer', () => ({
SshChannelMultiplexer: class {
request = request
+1 -1
View File
@@ -10,7 +10,7 @@ import type { SshConnection } from './ssh-connection'
import { SshChannelMultiplexer } from './ssh-channel-multiplexer'
import { shellEscape } from './ssh-connection-utils'
import { SHORT_RELAY_SOCKET_DIR_PREFIX } from './relay-socket-path-limit'
import { waitForSentinel } from './ssh-relay-deploy-helpers'
import { waitForSentinel } from './ssh-relay-sentinel'
const RELAY_PTY_COUNT_TIMEOUT_MS = 10_000
+1 -1
View File
@@ -7,7 +7,7 @@
* host with no PATH Node from reading every relay as unverifiable.
*/
import type { SshConnection } from './ssh-connection'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
const RELAY_DAEMON_MARKER = ' relay.js --detached '
@@ -1,16 +1,7 @@
import { EventEmitter } from 'node:events'
import { describe, expect, it, vi } from 'vitest'
import type { ClientChannel } from 'ssh2'
import { execCommand, waitForSentinel } from './ssh-relay-deploy-helpers'
import { RELAY_SENTINEL } from './relay-protocol'
import {
RelayVersionMismatchError,
RELAY_EXIT_CODE_VERSION_MISMATCH
} from './ssh-relay-version-mismatch-error'
import {
RelayCredentialMismatchError,
RELAY_EXIT_CODE_CREDENTIAL_MISMATCH
} from './ssh-relay-credential-mismatch-error'
import { execCommand } from './ssh-relay-exec-command'
type MockChannel = ClientChannel & {
stdin: EventEmitter & { write: ReturnType<typeof vi.fn> }
@@ -36,229 +27,6 @@ async function execCommandRejection(promise: Promise<string>): Promise<Error> {
)
}
describe('waitForSentinel', () => {
it('closes and rejects with AbortError while clearing startup resources on abort', async () => {
vi.useFakeTimers()
try {
const channel = createMockChannel()
const controller = new AbortController()
const removeListener = vi.spyOn(controller.signal, 'removeEventListener')
const transportPromise = waitForSentinel(channel, controller.signal)
expect(vi.getTimerCount()).toBe(1)
controller.abort()
await expect(transportPromise).rejects.toMatchObject({ name: 'AbortError' })
expect(channel.close).toHaveBeenCalledTimes(1)
expect(removeListener).toHaveBeenCalledWith('abort', expect.any(Function))
expect(vi.getTimerCount()).toBe(0)
} finally {
vi.useRealTimers()
}
})
it('removes the abort listener and timers when startup fails before the sentinel', async () => {
vi.useFakeTimers()
try {
const channel = createMockChannel()
const controller = new AbortController()
const removeListener = vi.spyOn(controller.signal, 'removeEventListener')
const transportPromise = waitForSentinel(channel, controller.signal)
channel.emit('error', new Error('remote host rebooted'))
await expect(transportPromise).rejects.toThrow('remote host rebooted')
expect(removeListener).toHaveBeenCalledWith('abort', expect.any(Function))
expect(vi.getTimerCount()).toBe(0)
} finally {
vi.useRealTimers()
}
})
it('buffers post-sentinel chunks until the transport subscribes', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
channel.emit('data', Buffer.from(RELAY_SENTINEL))
channel.emit('data', Buffer.from('first-frame-after-sentinel'))
const transport = await transportPromise
const chunks: string[] = []
transport.onData((chunk) => chunks.push(chunk.toString('utf-8')))
expect(chunks).toEqual(['first-frame-after-sentinel'])
})
it('buffers post-sentinel bytes from the sentinel chunk', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
channel.emit('data', Buffer.from(`${RELAY_SENTINEL}same-chunk-frame`))
const transport = await transportPromise
const chunks: string[] = []
transport.onData((chunk) => chunks.push(chunk.toString('utf-8')))
expect(chunks).toEqual(['same-chunk-frame'])
})
it('rejects on relay channel errors before the sentinel instead of emitting uncaught errors', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
expect(() => channel.emit('error', new Error('remote host rebooted'))).not.toThrow()
await expect(transportPromise).rejects.toThrow('remote host rebooted')
})
it('notifies transport close subscribers on relay channel errors after the sentinel', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
channel.emit('data', Buffer.from(RELAY_SENTINEL))
const transport = await transportPromise
const onClose = vi.fn()
transport.onClose(onClose)
expect(() => channel.emit('error', new Error('remote host rebooted'))).not.toThrow()
expect(onClose).toHaveBeenCalledTimes(1)
})
it('translates a pre-sentinel exit-42 + close into RelayVersionMismatchError', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
channel.stderr.emit(
'data',
Buffer.from(
'[relay-connect] Handshake mismatch: expected=0.1.0+aaa, daemon=0.1.0+bbb; exiting 42\n'
)
)
channel.emit('exit', RELAY_EXIT_CODE_VERSION_MISMATCH)
channel.emit('close')
await expect(transportPromise).rejects.toBeInstanceOf(RelayVersionMismatchError)
await transportPromise.catch((err: RelayVersionMismatchError) => {
expect(err.expected).toBe('0.1.0+aaa')
expect(err.got).toBe('0.1.0+bbb')
})
})
it('translates a pre-sentinel exit-42 even when the version detail is missing', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
channel.stderr.emit('data', Buffer.from('boom\n'))
channel.emit('exit', RELAY_EXIT_CODE_VERSION_MISMATCH)
channel.emit('close')
await expect(transportPromise).rejects.toBeInstanceOf(RelayVersionMismatchError)
})
it('translates a pre-sentinel exit-43 + close into RelayCredentialMismatchError', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
channel.stderr.emit(
'data',
Buffer.from('[relay-connect] Endpoint credential refused by daemon; exiting 43\n')
)
channel.emit('exit', RELAY_EXIT_CODE_CREDENTIAL_MISMATCH)
channel.emit('close')
await expect(transportPromise).rejects.toBeInstanceOf(RelayCredentialMismatchError)
await transportPromise.catch((err: unknown) => {
expect(err).not.toBeInstanceOf(RelayVersionMismatchError)
})
})
it('rejects with a generic error (not RelayVersionMismatchError) on a non-42 exit code', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
channel.stderr.emit('data', Buffer.from('node: bad bytecode\n'))
channel.emit('exit', 1)
channel.emit('close')
await expect(transportPromise).rejects.toThrow(/Relay process exited before ready/)
await transportPromise.catch((err: unknown) => {
expect(err).not.toBeInstanceOf(RelayVersionMismatchError)
})
})
it('rejects and closes the channel when pre-sentinel stdout exceeds the startup buffer cap', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
channel.emit('data', Buffer.alloc(65 * 1024, 'a'))
const outcome = await Promise.race([
transportPromise.then(
() => 'resolved',
(err: Error) => `rejected:${err.message}`
),
new Promise<string>((resolve) => setTimeout(() => resolve('pending'), 0))
])
if (outcome === 'pending') {
channel.emit('close')
await transportPromise.catch(() => {})
}
expect(outcome).toContain('Relay startup output exceeded')
expect(channel.close).toHaveBeenCalledTimes(1)
})
it('does not count same-chunk post-sentinel payload against the startup buffer cap', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
const postSentinelPayload = Buffer.alloc(70 * 1024, 'b')
channel.emit(
'data',
Buffer.concat([
Buffer.alloc(64 * 1024, 'a'),
Buffer.from(RELAY_SENTINEL),
postSentinelPayload
])
)
const transport = await transportPromise
const chunks: Buffer[] = []
transport.onData((chunk) => chunks.push(chunk))
expect(Buffer.concat(chunks)).toEqual(postSentinelPayload)
expect(channel.close).not.toHaveBeenCalled()
})
it.each(['ssh2 channel', 'system-SSH child stdio'])(
'forwards write(false), callback settlement, and drain for a %s',
async (channelKind) => {
const channel = createMockChannel()
if (channelKind.startsWith('system')) {
Object.assign(channel, { _process: new EventEmitter() })
}
const callback = vi.fn()
const drain = vi.fn()
channel.stdin.write.mockImplementation((...args: unknown[]) => {
const onWritten = args.find((arg) => typeof arg === 'function') as (
error?: Error | null
) => void
onWritten(null)
return false
})
const transportPromise = waitForSentinel(channel)
channel.emit('data', Buffer.from(RELAY_SENTINEL))
const transport = await transportPromise
transport.onDrain?.(drain)
expect(transport.write(Buffer.from('frame'), callback)).toBe(false)
expect(callback).toHaveBeenCalledWith({ ok: true })
channel.stdin.emit('drain')
expect(drain).toHaveBeenCalledOnce()
expect(transport.supportsWriteSettlement).toBe(true)
}
)
})
describe('execCommand', () => {
const installMarkerToken = 'a'.repeat(32)
const installMarkerPath = `/home/u/.install-lock/.sftp-namespace-${installMarkerToken}`
@@ -1,9 +1,13 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
import type { SshConnection } from './ssh-connection'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import {
isRelayGcClaimOwned,
releaseRelayGcClaim,
+1 -1
View File
@@ -1,5 +1,5 @@
import type { RemoteInstallTransport } from './remote-install-transport'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import type { RemoteHostPlatform } from './ssh-remote-platform'
export function execHostCommand(
@@ -1,12 +1,13 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { mkdtempSync, writeFileSync, rmSync, readFileSync } from 'node:fs'
import { join } from 'node:path'
import { tmpdir } from 'node:os'
import { createServer } from 'node:net'
import { describe, expect, it, vi } from 'vitest'
import { runProcess } from '@orca/process-host'
vi.mock('./ssh-relay-deploy-helpers', () => ({
execCommand: vi.fn(),
isUnconfirmedSshCommandTermination: () => false
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
import {
relayEndpointIncumbentProbeCommand,
@@ -1,9 +1,13 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
import type { SshConnection } from './ssh-connection'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { acquireInstallLock, isRelayInstallLockStale } from './ssh-relay-install-lock'
import { getRemoteHostPlatform } from './ssh-remote-platform'
+4 -2
View File
@@ -1,7 +1,9 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { SshConnection } from './ssh-connection'
vi.mock('./ssh-relay-deploy-helpers', () => ({
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn(),
isUnconfirmedSshCommandTermination: (error: unknown) =>
error instanceof Error &&
@@ -13,7 +15,7 @@ vi.mock('./ssh-relay-gc-claim', () => ({
waitForRelayGcClaimRelease: vi.fn().mockResolvedValue(undefined)
}))
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { acquireInstallLock, RemoteInstallLockBusyError } from './ssh-relay-install-lock'
import { getRemoteHostPlatform } from './ssh-remote-platform'
@@ -2,7 +2,7 @@ import { EventEmitter } from 'node:events'
import type { ClientChannel } from 'ssh2'
import { expect, it, vi } from 'vitest'
import { RELAY_SENTINEL } from './relay-protocol'
import { waitForSentinel } from './ssh-relay-deploy-helpers'
import { waitForSentinel } from './ssh-relay-sentinel'
it.each([1, 256])('copies each startup prefix once across %i chunks', async (chunks) => {
const channel = Object.assign(new EventEmitter(), {
+249
View File
@@ -0,0 +1,249 @@
import { EventEmitter } from 'node:events'
import { describe, expect, it, vi } from 'vitest'
import type { ClientChannel } from 'ssh2'
import { waitForSentinel } from './ssh-relay-sentinel'
import { RELAY_SENTINEL } from './relay-protocol'
import {
RelayVersionMismatchError,
RELAY_EXIT_CODE_VERSION_MISMATCH
} from './ssh-relay-version-mismatch-error'
import {
RelayCredentialMismatchError,
RELAY_EXIT_CODE_CREDENTIAL_MISMATCH
} from './ssh-relay-credential-mismatch-error'
type MockChannel = ClientChannel & {
stdin: EventEmitter & { write: ReturnType<typeof vi.fn> }
}
function createMockChannel(): MockChannel {
return Object.assign(new EventEmitter(), {
stderr: Object.assign(new EventEmitter(), { resume: vi.fn() }),
stdin: Object.assign(new EventEmitter(), { write: vi.fn(() => true) }),
close: vi.fn(),
resume: vi.fn()
}) as unknown as MockChannel
}
describe('waitForSentinel', () => {
it('closes and rejects with AbortError while clearing startup resources on abort', async () => {
vi.useFakeTimers()
try {
const channel = createMockChannel()
const controller = new AbortController()
const removeListener = vi.spyOn(controller.signal, 'removeEventListener')
const transportPromise = waitForSentinel(channel, controller.signal)
expect(vi.getTimerCount()).toBe(1)
controller.abort()
await expect(transportPromise).rejects.toMatchObject({ name: 'AbortError' })
expect(channel.close).toHaveBeenCalledTimes(1)
expect(removeListener).toHaveBeenCalledWith('abort', expect.any(Function))
expect(vi.getTimerCount()).toBe(0)
} finally {
vi.useRealTimers()
}
})
it('removes the abort listener and timers when startup fails before the sentinel', async () => {
vi.useFakeTimers()
try {
const channel = createMockChannel()
const controller = new AbortController()
const removeListener = vi.spyOn(controller.signal, 'removeEventListener')
const transportPromise = waitForSentinel(channel, controller.signal)
channel.emit('error', new Error('remote host rebooted'))
await expect(transportPromise).rejects.toThrow('remote host rebooted')
expect(removeListener).toHaveBeenCalledWith('abort', expect.any(Function))
expect(vi.getTimerCount()).toBe(0)
} finally {
vi.useRealTimers()
}
})
it('buffers post-sentinel chunks until the transport subscribes', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
channel.emit('data', Buffer.from(RELAY_SENTINEL))
channel.emit('data', Buffer.from('first-frame-after-sentinel'))
const transport = await transportPromise
const chunks: string[] = []
transport.onData((chunk) => chunks.push(chunk.toString('utf-8')))
expect(chunks).toEqual(['first-frame-after-sentinel'])
})
it('buffers post-sentinel bytes from the sentinel chunk', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
channel.emit('data', Buffer.from(`${RELAY_SENTINEL}same-chunk-frame`))
const transport = await transportPromise
const chunks: string[] = []
transport.onData((chunk) => chunks.push(chunk.toString('utf-8')))
expect(chunks).toEqual(['same-chunk-frame'])
})
it('rejects on relay channel errors before the sentinel instead of emitting uncaught errors', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
expect(() => channel.emit('error', new Error('remote host rebooted'))).not.toThrow()
await expect(transportPromise).rejects.toThrow('remote host rebooted')
})
it('notifies transport close subscribers on relay channel errors after the sentinel', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
channel.emit('data', Buffer.from(RELAY_SENTINEL))
const transport = await transportPromise
const onClose = vi.fn()
transport.onClose(onClose)
expect(() => channel.emit('error', new Error('remote host rebooted'))).not.toThrow()
expect(onClose).toHaveBeenCalledTimes(1)
})
it('translates a pre-sentinel exit-42 + close into RelayVersionMismatchError', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
channel.stderr.emit(
'data',
Buffer.from(
'[relay-connect] Handshake mismatch: expected=0.1.0+aaa, daemon=0.1.0+bbb; exiting 42\n'
)
)
channel.emit('exit', RELAY_EXIT_CODE_VERSION_MISMATCH)
channel.emit('close')
await expect(transportPromise).rejects.toBeInstanceOf(RelayVersionMismatchError)
await transportPromise.catch((err: RelayVersionMismatchError) => {
expect(err.expected).toBe('0.1.0+aaa')
expect(err.got).toBe('0.1.0+bbb')
})
})
it('translates a pre-sentinel exit-42 even when the version detail is missing', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
channel.stderr.emit('data', Buffer.from('boom\n'))
channel.emit('exit', RELAY_EXIT_CODE_VERSION_MISMATCH)
channel.emit('close')
await expect(transportPromise).rejects.toBeInstanceOf(RelayVersionMismatchError)
})
it('translates a pre-sentinel exit-43 + close into RelayCredentialMismatchError', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
channel.stderr.emit(
'data',
Buffer.from('[relay-connect] Endpoint credential refused by daemon; exiting 43\n')
)
channel.emit('exit', RELAY_EXIT_CODE_CREDENTIAL_MISMATCH)
channel.emit('close')
await expect(transportPromise).rejects.toBeInstanceOf(RelayCredentialMismatchError)
await transportPromise.catch((err: unknown) => {
expect(err).not.toBeInstanceOf(RelayVersionMismatchError)
})
})
it('rejects with a generic error (not RelayVersionMismatchError) on a non-42 exit code', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
channel.stderr.emit('data', Buffer.from('node: bad bytecode\n'))
channel.emit('exit', 1)
channel.emit('close')
await expect(transportPromise).rejects.toThrow(/Relay process exited before ready/)
await transportPromise.catch((err: unknown) => {
expect(err).not.toBeInstanceOf(RelayVersionMismatchError)
})
})
it('rejects and closes the channel when pre-sentinel stdout exceeds the startup buffer cap', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
channel.emit('data', Buffer.alloc(65 * 1024, 'a'))
const outcome = await Promise.race([
transportPromise.then(
() => 'resolved',
(err: Error) => `rejected:${err.message}`
),
new Promise<string>((resolve) => setTimeout(() => resolve('pending'), 0))
])
if (outcome === 'pending') {
channel.emit('close')
await transportPromise.catch(() => {})
}
expect(outcome).toContain('Relay startup output exceeded')
expect(channel.close).toHaveBeenCalledTimes(1)
})
it('does not count same-chunk post-sentinel payload against the startup buffer cap', async () => {
const channel = createMockChannel()
const transportPromise = waitForSentinel(channel)
const postSentinelPayload = Buffer.alloc(70 * 1024, 'b')
channel.emit(
'data',
Buffer.concat([
Buffer.alloc(64 * 1024, 'a'),
Buffer.from(RELAY_SENTINEL),
postSentinelPayload
])
)
const transport = await transportPromise
const chunks: Buffer[] = []
transport.onData((chunk) => chunks.push(chunk))
expect(Buffer.concat(chunks)).toEqual(postSentinelPayload)
expect(channel.close).not.toHaveBeenCalled()
})
it.each(['ssh2 channel', 'system-SSH child stdio'])(
'forwards write(false), callback settlement, and drain for a %s',
async (channelKind) => {
const channel = createMockChannel()
if (channelKind.startsWith('system')) {
Object.assign(channel, { _process: new EventEmitter() })
}
const callback = vi.fn()
const drain = vi.fn()
channel.stdin.write.mockImplementation((...args: unknown[]) => {
const onWritten = args.find((arg) => typeof arg === 'function') as (
error?: Error | null
) => void
onWritten(null)
return false
})
const transportPromise = waitForSentinel(channel)
channel.emit('data', Buffer.from(RELAY_SENTINEL))
const transport = await transportPromise
transport.onDrain?.(drain)
expect(transport.write(Buffer.from('frame'), callback)).toBe(false)
expect(callback).toHaveBeenCalledWith({ ok: true })
channel.stdin.emit('drain')
expect(drain).toHaveBeenCalledOnce()
expect(transport.supportsWriteSettlement).toBe(true)
}
)
})
@@ -4,11 +4,6 @@ import { RELAY_SENTINEL, RELAY_SENTINEL_TIMEOUT_MS } from './relay-protocol'
import type { MultiplexerTransport } from './ssh-channel-multiplexer'
import { buildRelayHandshakeRefusalError } from './ssh-relay-handshake-mismatch'
export { uploadFile, uploadDirectory, mkdirSftp } from './sftp-upload'
export { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
// ── Sentinel detection ────────────────────────────────────────────────
const MAX_RELAY_STARTUP_BUFFER_BYTES = 64 * 1024
const RELAY_SENTINEL_BUFFER = Buffer.from(RELAY_SENTINEL, 'utf-8')
@@ -1,10 +1,14 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: vi.fn()
}))
import type { SshConnection } from './ssh-connection'
import { REMOTE_INSTALL_MODELS } from './remote-install-model'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import {
abandonInstall,
finalizeInstall,
@@ -1,6 +1,6 @@
import type { SshConnection } from './ssh-connection'
import { createSshOperationAbortError } from './ssh-connection-utils'
import { execCommand } from './ssh-relay-deploy-helpers'
import { execCommand } from './ssh-relay-exec-command'
import { isSshSessionLimitError } from './ssh-session-limit-error'
export type RemoteNodeResolutionOptions = {
@@ -1,3 +1,4 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { execFileSync } from 'node:child_process'
import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import os from 'node:os'
@@ -8,7 +9,8 @@ import { getRemoteHostPlatform } from './ssh-remote-platform'
const execCommandMock = vi.hoisted(() => vi.fn())
vi.mock('./ssh-relay-deploy-helpers', () => ({
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: execCommandMock
}))
+1 -2
View File
@@ -7,8 +7,7 @@ import {
buildPosixNodeInstallGuidance,
type RemoteNodeResolutionOptions
} from './ssh-remote-node-install-guidance'
import { execCommand } from './ssh-relay-deploy-helpers'
import { isSshCommandExitError } from './ssh-relay-exec-command'
import { execCommand, isSshCommandExitError } from './ssh-relay-exec-command'
import {
buildPosixNodeToolchainProbe,
buildWindowsNodeToolchainProbe,
@@ -1,10 +1,12 @@
import type * as ExecCommandModule from './ssh-relay-exec-command'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { SshConnection } from './ssh-connection'
import { SSH_EXEC_TIMEOUT_CODE } from './ssh-relay-exec-command'
const execCommandMock = vi.hoisted(() => vi.fn())
vi.mock('./ssh-relay-deploy-helpers', () => ({
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
...(await importOriginal<typeof ExecCommandModule>()),
execCommand: execCommandMock
}))
@@ -4,8 +4,11 @@ import {
iterateProcessOutputLines
} from '../../shared/process-output-field-scanner'
import { parseUnameToRelayPlatform, type RelayPlatform } from './relay-protocol'
import { execCommand } from './ssh-relay-deploy-helpers'
import { isSshExecTimeout, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import {
execCommand,
isSshExecTimeout,
isUnconfirmedSshCommandTermination
} from './ssh-relay-exec-command'
import { isSshSessionLimitError } from './ssh-session-limit-error'
import { getRemoteHostPlatform, type RemoteHostPlatform } from './ssh-remote-platform'
import { powerShellCommand } from './ssh-remote-powershell'
@@ -28,7 +28,7 @@ export class SystemSshPortForwardProvider implements SshPortForwardProvider {
// Why: this stderr stays attached for the forward's whole lifetime but is
// only used to build the exit-error detail, so keep a bounded tail — a chatty
// remote sshd could otherwise grow it unbounded on a long-lived forward
// (mirrors MAX_RELAY_STARTUP_BUFFER_BYTES in ssh-relay-deploy-helpers).
// (mirrors MAX_RELAY_STARTUP_BUFFER_BYTES in ssh-relay-sentinel).
const MAX_STDERR_TAIL_BYTES = 64 * 1024
let stderr = ''
const onStderr = (chunk: Buffer): void => {