From 019417bd86bee9f571de3af313f402abb75742ca Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sun, 18 Oct 2026 02:34:30 -0700 Subject: [PATCH] refactor(ssh): import the system-ssh and ssh -G modules directly, not through re-export shims (#30255) Deletes ssh-system-fallback.ts and the re-export lines in ssh-config-parser.ts, ssh-connection.ts, system-ssh-file-binary-transfer.ts and ssh-remote-powershell.ts; importers name the real modules. SshConnection tests mock each real module, keeping the parser stub that holds ~/.ssh/config out. --- config/reliability-gates.jsonc | 2 +- config/scripts/ci-shard-timings.json | 2 +- .../ipc/ssh-connection-state-callbacks.ts | 2 +- .../ssh/orcad-remote-node-runtime-windows.ts | 3 +- src/main/ssh/orcad-remote-windows-node.ts | 2 +- .../ssh/remote-node-runtime-store-windows.ts | 3 +- src/main/ssh/ssh-auth-resolution.ts | 2 +- src/main/ssh/ssh-config-parser.test.ts | 3 +- src/main/ssh/ssh-config-parser.ts | 1 - src/main/ssh/ssh-config-resolver.test.ts | 2 +- .../ssh/ssh-connection-auth-fallback.test.ts | 24 +++++++- .../ssh/ssh-connection-channel-error.test.ts | 22 +++++++- .../ssh/ssh-connection-channel-open.test.ts | 22 +++++++- .../ssh/ssh-connection-direct-startup.test.ts | 24 +++++++- .../ssh/ssh-connection-file-transfer.test.ts | 31 +++++++--- src/main/ssh/ssh-connection-file-transfers.ts | 9 ++- .../ssh/ssh-connection-github-probe.test.ts | 24 +++++++- .../ssh-connection-gssapi-fallback.test.ts | 24 +++++++- ...h-connection-host-key-store-wiring.test.ts | 6 +- ...h-connection-host-key-verification.test.ts | 22 +++++++- .../ssh-connection-manager-registry.test.ts | 22 +++++++- ...h-connection-manager-sign-in-reuse.test.ts | 22 +++++++- src/main/ssh/ssh-connection-manager.ts | 7 +-- ...onnection-reconnect-handshake-loss.test.ts | 22 +++++++- .../ssh-connection-reconnect-ladder.test.ts | 24 +++++++- .../ssh/ssh-connection-sftp-namespace.test.ts | 29 +++++++--- .../ssh-connection-system-transport.test.ts | 29 ++++++++-- src/main/ssh/ssh-connection-test-fixtures.ts | 4 +- src/main/ssh/ssh-connection-test-harness.ts | 56 ++++++++++++------- src/main/ssh/ssh-connection-utils.test.ts | 2 +- src/main/ssh/ssh-connection-utils.ts | 2 +- .../ssh/ssh-connection-wake-retry.test.ts | 24 +++++++- src/main/ssh/ssh-connection.test.ts | 22 +++++++- src/main/ssh/ssh-connection.ts | 17 +++--- src/main/ssh/ssh-control-socket.ts | 2 +- ...credential-prompt-login-grace-wire.test.ts | 8 +-- .../ssh/ssh-host-relay-windows-inventory.ts | 5 +- .../ssh/ssh-keyboard-interactive-wire.test.ts | 8 +-- .../ssh-multi-factor-authentication.test.ts | 2 +- .../ssh/ssh-multi-key-authentication.test.ts | 2 +- src/main/ssh/ssh-proxy-command.ts | 2 +- src/main/ssh/ssh-relay-gc-claim.ts | 3 +- .../ssh/ssh-relay-install-lock-commands.ts | 3 +- .../ssh-relay-install-lock-exited-owner.ts | 2 +- .../ssh/ssh-relay-windows-launch-command.ts | 2 +- src/main/ssh/ssh-remote-commands.ts | 3 +- src/main/ssh/ssh-remote-directory-browse.ts | 3 +- .../ssh/ssh-remote-node-toolchain-probe.ts | 2 +- src/main/ssh/ssh-remote-powershell.ts | 4 -- src/main/ssh/ssh-system-fallback.ts | 13 ----- .../ssh-system-transport.integration.test.ts | 2 +- src/main/ssh/ssh-transport-selection.ts | 2 +- ...system-ssh-dynamic-forward-process.test.ts | 5 +- .../ssh/system-ssh-dynamic-forward-process.ts | 3 +- .../ssh/system-ssh-file-binary-transfer.ts | 9 +-- src/main/ssh/system-ssh-file-transfer.ts | 7 +-- src/main/ssh/system-ssh-forward-process.ts | 3 +- ....test.ts => system-ssh-operations.test.ts} | 12 ++-- src/main/ssh/system-ssh-windows-file-write.ts | 3 +- .../ssh/system-ssh-windows-upload.test.ts | 11 ++-- 60 files changed, 454 insertions(+), 179 deletions(-) delete mode 100644 src/main/ssh/ssh-system-fallback.ts rename src/main/ssh/{ssh-system-fallback.test.ts => system-ssh-operations.test.ts} (99%) diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index 986745923c2..efe80880263 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -6349,7 +6349,7 @@ "pnpm exec vitest run --config config/vitest.config.ts src/shared/browser-client-host-protocol.test.ts src/main/browser/browser-client-host-attach-request.test.ts src/main/browser/browser-client-network-route-registry.test.ts src/main/browser/paired-runtime-browser-client-host-composition.test.ts src/main/browser/paired-runtime-browser-client-host.test.ts src/main/browser/paired-runtime-browser-client-host-reconnect.test.ts src/main/browser/paired-runtime-browser-host-lease.test.ts src/main/browser/paired-runtime-browser-host-lease-reconnect.test.ts src/main/browser/browser-host-lease-reconnect-delay.test.ts src/main/browser/paired-runtime-browser-network-route.test.ts src/main/runtime/browser-host/browser-host-command-ledger.test.ts src/main/runtime/browser-host/browser-host-command-ledger-capacity.test.ts src/main/runtime/browser-host/browser-host-lease-placement-retirement.test.ts src/main/runtime/browser-host/browser-host-lease-registry.test.ts src/main/runtime/runtime-rpc-browser-host-admission.test.ts src/main/runtime/rpc/methods/browser-client-host.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/main/browser/browser-client-network-route-registry.test.ts src/main/browser/paired-runtime-browser-client-host-composition.test.ts src/main/browser/paired-runtime-browser-client-host-registry.test.ts src/main/browser/paired-runtime-browser-client-host-runtime.test.ts src/main/browser/browser-client-page-command-executor.test.ts src/main/browser/browser-session-startup.test.ts src/main/ipc/runtime-environments-subscription-teardown.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/browser-host/browser-host-command-ledger.test.ts src/main/runtime/browser-host/browser-host-command-ledger-capacity.test.ts src/main/runtime/browser-host/browser-host-lease-registry.test.ts src/main/runtime/rpc/methods/browser-client-host.test.ts", - "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/browser-host/browser-host-lease-registry.test.ts src/main/browser/browser-network-deferred-socket.test.ts src/main/browser/browser-network-execution-route.test.ts src/main/browser/paired-runtime-browser-network-route.test.ts src/main/runtime/rpc/methods/browser-network-tunnel.test.ts src/main/browser/ssh-browser-network-execution-route.test.ts src/main/browser/system-ssh-socks-client-socket.test.ts src/main/ssh/system-ssh-dynamic-forward-process.test.ts src/shared/browser-client-host-protocol.test.ts src/main/ssh/system-ssh-forward-process.test.ts src/main/ssh/ssh-system-fallback.test.ts src/main/ssh/ssh-port-forward.test.ts", + "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/browser-host/browser-host-lease-registry.test.ts src/main/browser/browser-network-deferred-socket.test.ts src/main/browser/browser-network-execution-route.test.ts src/main/browser/paired-runtime-browser-network-route.test.ts src/main/runtime/rpc/methods/browser-network-tunnel.test.ts src/main/browser/ssh-browser-network-execution-route.test.ts src/main/browser/system-ssh-socks-client-socket.test.ts src/main/ssh/system-ssh-dynamic-forward-process.test.ts src/shared/browser-client-host-protocol.test.ts src/main/ssh/system-ssh-forward-process.test.ts src/main/ssh/system-ssh-operations.test.ts src/main/ssh/ssh-port-forward.test.ts", "ORCA_RUN_DOCKER_SSH_BROWSER_E2E=1 pnpm exec vitest run --config config/vitest.config.ts tests/e2e/ssh-browser-network-execution-route.docker.unit.test.ts", "ORCA_RUN_DOCKER_SSH_BROWSER_E2E=1 node_modules/.bin/vitest run --config config/vitest.config.ts tests/e2e/ssh-browser-network-execution-route.docker.unit.test.ts" ], diff --git a/config/scripts/ci-shard-timings.json b/config/scripts/ci-shard-timings.json index 4d380d3b27f..51878065b1b 100644 --- a/config/scripts/ci-shard-timings.json +++ b/config/scripts/ci-shard-timings.json @@ -5802,7 +5802,7 @@ "src/main/ssh/ssh-request-outcome-verdict.test.ts": 37, "src/main/ssh/ssh-security-key-identity.test.ts": 147, "src/main/ssh/ssh-session-limit-error.test.ts": 27, - "src/main/ssh/ssh-system-fallback.test.ts": 275, + "src/main/ssh/system-ssh-operations.test.ts": 275, "src/main/ssh/ssh-system-transport.integration.test.ts": 277, "src/main/ssh/ssh-target-id-migration.test.ts": 46, "src/main/ssh/ssh-target-orcad-claims.test.ts": 306, diff --git a/src/main/ipc/ssh-connection-state-callbacks.ts b/src/main/ipc/ssh-connection-state-callbacks.ts index c20e4c76149..ccaa40fb979 100644 --- a/src/main/ipc/ssh-connection-state-callbacks.ts +++ b/src/main/ipc/ssh-connection-state-callbacks.ts @@ -1,4 +1,4 @@ -import type { SshConnectionCallbacks } from '../ssh/ssh-connection' +import type { SshConnectionCallbacks } from '../ssh/ssh-connection-utils' import type { SshConnectionState } from '../../shared/ssh-types' import { isSshHostCensusInFlight } from '../ssh/ssh-connection-attribution' import { clearSshCredentialDeclined, isSshCredentialDeclined } from '../ssh/ssh-credential-decline' diff --git a/src/main/ssh/orcad-remote-node-runtime-windows.ts b/src/main/ssh/orcad-remote-node-runtime-windows.ts index d3b28e46b5c..2c097c4d11c 100644 --- a/src/main/ssh/orcad-remote-node-runtime-windows.ts +++ b/src/main/ssh/orcad-remote-node-runtime-windows.ts @@ -21,7 +21,8 @@ import { REMOTE_NODE_RUNTIME_SELFTEST_FAILED, REMOTE_NODE_RUNTIME_VERIFIED_MARKER } from './orcad-remote-node-runtime-report' -import { powerShellCommand, powerShellLiteral } from './ssh-remote-powershell' +import { powerShellCommand } from './ssh-remote-powershell' +import { quotePowerShellLiteral as powerShellLiteral } from '../../shared/powershell-native-argument' function windowsPath(...segments: string[]): string { return segments diff --git a/src/main/ssh/orcad-remote-windows-node.ts b/src/main/ssh/orcad-remote-windows-node.ts index 4ca1737e226..e0f3f2ad704 100644 --- a/src/main/ssh/orcad-remote-windows-node.ts +++ b/src/main/ssh/orcad-remote-windows-node.ts @@ -22,7 +22,7 @@ import type { OrcadHostTransport } from './orcad-host-transport' 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' +import { quotePowerShellLiteral as powerShellLiteral } from '../../shared/powershell-native-argument' import { ORCAD_WINDOWS_COMMAND_LINE_UNSAFE_CODE } from '../orcad/orcad-host-unavailable' import { ORCAD_WINDOWS_HOST_SCRIPT, diff --git a/src/main/ssh/remote-node-runtime-store-windows.ts b/src/main/ssh/remote-node-runtime-store-windows.ts index c3335f71cb0..f4efd09a543 100644 --- a/src/main/ssh/remote-node-runtime-store-windows.ts +++ b/src/main/ssh/remote-node-runtime-store-windows.ts @@ -24,7 +24,8 @@ import { } from './remote-node-runtime-store-inventory' import { RELAY_REMOTE_DIR } from './relay-protocol' import { isWindowsRemoteHost, joinRemotePath, type RemoteHostPlatform } from './ssh-remote-platform' -import { powerShellCommand, powerShellLiteral } from './ssh-remote-powershell' +import { powerShellCommand } from './ssh-remote-powershell' +import { quotePowerShellLiteral as powerShellLiteral } from '../../shared/powershell-native-argument' const REPARSE = '[IO.FileAttributes]::ReparsePoint' diff --git a/src/main/ssh/ssh-auth-resolution.ts b/src/main/ssh/ssh-auth-resolution.ts index 70fd76eb6fe..86b4bb3dc44 100644 --- a/src/main/ssh/ssh-auth-resolution.ts +++ b/src/main/ssh/ssh-auth-resolution.ts @@ -1,7 +1,7 @@ import { existsSync, readFileSync } from 'node:fs' import { utils, type BaseAgent, type ParsedKey } from 'ssh2' import type { SshTarget } from '../../shared/ssh-types' -import type { SshResolvedConfig } from './ssh-config-parser' +import type { SshResolvedConfig } from './ssh-g-config-resolution' import { createIdentityFilteredAgent } from './ssh-agent-identity-filter' import { resolveSshConfigHomePath } from './ssh-config-path-expansion' import { isOpenSshConfigBackedTarget } from './system-ssh-args' diff --git a/src/main/ssh/ssh-config-parser.test.ts b/src/main/ssh/ssh-config-parser.test.ts index 8625a553ad1..12081c38b0c 100644 --- a/src/main/ssh/ssh-config-parser.test.ts +++ b/src/main/ssh/ssh-config-parser.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it, vi } from 'vitest' import { join } from 'node:path' -import { parseSshConfig, sshConfigHostsToTargets, parseSshGOutput } from './ssh-config-parser' +import { parseSshConfig, sshConfigHostsToTargets } from './ssh-config-parser' +import { parseSshGOutput } from './ssh-g-config-resolution' import { searchSshConfigHosts } from './ssh-config-host-picker' const sshConfigHostsToSummaries = ( diff --git a/src/main/ssh/ssh-config-parser.ts b/src/main/ssh/ssh-config-parser.ts index bdad3b383ab..53575749255 100644 --- a/src/main/ssh/ssh-config-parser.ts +++ b/src/main/ssh/ssh-config-parser.ts @@ -4,7 +4,6 @@ import { homedir } from 'node:os' import type { SshTarget } from '../../shared/ssh-types' import { expandSshConfigIncludes } from './ssh-config-include-expander' import { resolveSshConfigHomePath } from './ssh-config-path-expansion' -export { parseSshGOutput, resolveWithSshG, type SshResolvedConfig } from './ssh-g-config-resolution' export type SshConfigHost = { host: string diff --git a/src/main/ssh/ssh-config-resolver.test.ts b/src/main/ssh/ssh-config-resolver.test.ts index fe2d61bf06e..22871df7ba0 100644 --- a/src/main/ssh/ssh-config-resolver.test.ts +++ b/src/main/ssh/ssh-config-resolver.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { resolveWithSshG } from './ssh-config-parser' +import { resolveWithSshG } from './ssh-g-config-resolution' import { runProcess } from '@orca/process-host' vi.mock('os', () => ({ diff --git a/src/main/ssh/ssh-connection-auth-fallback.test.ts b/src/main/ssh/ssh-connection-auth-fallback.test.ts index 09b8af9e8a1..4fdd70fb4c5 100644 --- a/src/main/ssh/ssh-connection-auth-fallback.test.ts +++ b/src/main/ssh/ssh-connection-auth-fallback.test.ts @@ -16,14 +16,27 @@ import { walkInitialAuthLadder } from './ssh-connection-test-fixtures' import { SshConnection } from './ssh-connection' -import { resolveWithSshG } from './ssh-config-parser' +import { resolveWithSshG } from './ssh-g-config-resolution' vi.mock('ssh2', async () => (await import('./ssh-connection-test-harness')).createSsh2Module()) vi.mock('./system-ssh-binary', async () => (await import('./ssh-connection-test-harness')).createSystemSshBinaryModule() ) -vi.mock('./ssh-system-fallback', async () => - (await import('./ssh-connection-test-harness')).createSystemFallbackModule() +vi.mock('./system-ssh-args', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshArgsMocks(await importOriginal()) +) +vi.mock('./system-ssh-command', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshCommandMocks(await importOriginal()) +) +vi.mock('./system-ssh-file-binary-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileBinaryTransferMocks( + await importOriginal() + ) +) +vi.mock('./system-ssh-file-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileTransferMocks( + await importOriginal() + ) ) vi.mock('./ssh-control-socket', async () => (await import('./ssh-connection-test-harness')).createControlSocketModule() @@ -31,6 +44,11 @@ vi.mock('./ssh-control-socket', async () => vi.mock('./ssh-config-parser', async () => (await import('./ssh-connection-test-harness')).createSshConfigParserModule() ) +vi.mock('./ssh-g-config-resolution', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSshGConfigResolutionMocks( + await importOriginal() + ) +) describe('SshConnection', () => { beforeEach(() => { diff --git a/src/main/ssh/ssh-connection-channel-error.test.ts b/src/main/ssh/ssh-connection-channel-error.test.ts index 98cba4d547c..5d742020034 100644 --- a/src/main/ssh/ssh-connection-channel-error.test.ts +++ b/src/main/ssh/ssh-connection-channel-error.test.ts @@ -9,8 +9,21 @@ vi.mock('ssh2', async () => (await import('./ssh-connection-test-harness')).crea vi.mock('./system-ssh-binary', async () => (await import('./ssh-connection-test-harness')).createSystemSshBinaryModule() ) -vi.mock('./ssh-system-fallback', async () => - (await import('./ssh-connection-test-harness')).createSystemFallbackModule() +vi.mock('./system-ssh-args', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshArgsMocks(await importOriginal()) +) +vi.mock('./system-ssh-command', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshCommandMocks(await importOriginal()) +) +vi.mock('./system-ssh-file-binary-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileBinaryTransferMocks( + await importOriginal() + ) +) +vi.mock('./system-ssh-file-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileTransferMocks( + await importOriginal() + ) ) vi.mock('./ssh-control-socket', async () => (await import('./ssh-connection-test-harness')).createControlSocketModule() @@ -18,6 +31,11 @@ vi.mock('./ssh-control-socket', async () => vi.mock('./ssh-config-parser', async () => (await import('./ssh-connection-test-harness')).createSshConfigParserModule() ) +vi.mock('./ssh-g-config-resolution', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSshGConfigResolutionMocks( + await importOriginal() + ) +) beforeEach(resetSshConnectionMocks) afterEach(() => { diff --git a/src/main/ssh/ssh-connection-channel-open.test.ts b/src/main/ssh/ssh-connection-channel-open.test.ts index 58d4a5633b2..6ae28e10e72 100644 --- a/src/main/ssh/ssh-connection-channel-open.test.ts +++ b/src/main/ssh/ssh-connection-channel-open.test.ts @@ -14,8 +14,21 @@ vi.mock('ssh2', async () => (await import('./ssh-connection-test-harness')).crea vi.mock('./system-ssh-binary', async () => (await import('./ssh-connection-test-harness')).createSystemSshBinaryModule() ) -vi.mock('./ssh-system-fallback', async () => - (await import('./ssh-connection-test-harness')).createSystemFallbackModule() +vi.mock('./system-ssh-args', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshArgsMocks(await importOriginal()) +) +vi.mock('./system-ssh-command', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshCommandMocks(await importOriginal()) +) +vi.mock('./system-ssh-file-binary-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileBinaryTransferMocks( + await importOriginal() + ) +) +vi.mock('./system-ssh-file-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileTransferMocks( + await importOriginal() + ) ) vi.mock('./ssh-control-socket', async () => (await import('./ssh-connection-test-harness')).createControlSocketModule() @@ -23,6 +36,11 @@ vi.mock('./ssh-control-socket', async () => vi.mock('./ssh-config-parser', async () => (await import('./ssh-connection-test-harness')).createSshConfigParserModule() ) +vi.mock('./ssh-g-config-resolution', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSshGConfigResolutionMocks( + await importOriginal() + ) +) describe('SshConnection', () => { beforeEach(() => { diff --git a/src/main/ssh/ssh-connection-direct-startup.test.ts b/src/main/ssh/ssh-connection-direct-startup.test.ts index 51bdea4a3f1..10b7731374c 100644 --- a/src/main/ssh/ssh-connection-direct-startup.test.ts +++ b/src/main/ssh/ssh-connection-direct-startup.test.ts @@ -4,14 +4,27 @@ import { resetSshConnectionMocks, spawnSystemSshCommandMock } from './ssh-connec import { createCallbacks, createResolvedConfig, createTarget } from './ssh-connection-test-fixtures' import type { createSystemCommandChannel } from './ssh-connection-test-fixtures' import { SshConnection } from './ssh-connection' -import { resolveWithSshG } from './ssh-config-parser' +import { resolveWithSshG } from './ssh-g-config-resolution' vi.mock('ssh2', async () => (await import('./ssh-connection-test-harness')).createSsh2Module()) vi.mock('./system-ssh-binary', async () => (await import('./ssh-connection-test-harness')).createSystemSshBinaryModule() ) -vi.mock('./ssh-system-fallback', async () => - (await import('./ssh-connection-test-harness')).createSystemFallbackModule() +vi.mock('./system-ssh-args', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshArgsMocks(await importOriginal()) +) +vi.mock('./system-ssh-command', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshCommandMocks(await importOriginal()) +) +vi.mock('./system-ssh-file-binary-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileBinaryTransferMocks( + await importOriginal() + ) +) +vi.mock('./system-ssh-file-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileTransferMocks( + await importOriginal() + ) ) vi.mock('./ssh-control-socket', async () => (await import('./ssh-connection-test-harness')).createControlSocketModule() @@ -19,6 +32,11 @@ vi.mock('./ssh-control-socket', async () => vi.mock('./ssh-config-parser', async () => (await import('./ssh-connection-test-harness')).createSshConfigParserModule() ) +vi.mock('./ssh-g-config-resolution', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSshGConfigResolutionMocks( + await importOriginal() + ) +) describe('SshConnection', () => { beforeEach(() => { diff --git a/src/main/ssh/ssh-connection-file-transfer.test.ts b/src/main/ssh/ssh-connection-file-transfer.test.ts index 610d24c08d9..3b176ccb125 100644 --- a/src/main/ssh/ssh-connection-file-transfer.test.ts +++ b/src/main/ssh/ssh-connection-file-transfer.test.ts @@ -2,22 +2,34 @@ import { describe, expect, it, vi, beforeEach } from 'vitest' import { resetSshConnectionMocks } from './ssh-connection-test-harness' import { createCallbacks, createResolvedConfig, createTarget } from './ssh-connection-test-fixtures' import { SshConnection } from './ssh-connection' -import { resolveWithSshG } from './ssh-config-parser' +import { resolveWithSshG } from './ssh-g-config-resolution' import { downloadFileViaSystemSsh, - uploadDirectoryViaSystemSsh, uploadFileViaSystemSsh, - writeBufferViaSystemSsh, - writeFileViaSystemSsh -} from './ssh-system-fallback' + writeBufferViaSystemSsh +} from './system-ssh-file-binary-transfer' +import { uploadDirectoryViaSystemSsh, writeFileViaSystemSsh } from './system-ssh-file-transfer' import { getRemoteHostPlatform } from './ssh-remote-platform' vi.mock('ssh2', async () => (await import('./ssh-connection-test-harness')).createSsh2Module()) vi.mock('./system-ssh-binary', async () => (await import('./ssh-connection-test-harness')).createSystemSshBinaryModule() ) -vi.mock('./ssh-system-fallback', async () => - (await import('./ssh-connection-test-harness')).createSystemFallbackModule() +vi.mock('./system-ssh-args', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshArgsMocks(await importOriginal()) +) +vi.mock('./system-ssh-command', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshCommandMocks(await importOriginal()) +) +vi.mock('./system-ssh-file-binary-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileBinaryTransferMocks( + await importOriginal() + ) +) +vi.mock('./system-ssh-file-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileTransferMocks( + await importOriginal() + ) ) vi.mock('./ssh-control-socket', async () => (await import('./ssh-connection-test-harness')).createControlSocketModule() @@ -25,6 +37,11 @@ vi.mock('./ssh-control-socket', async () => vi.mock('./ssh-config-parser', async () => (await import('./ssh-connection-test-harness')).createSshConfigParserModule() ) +vi.mock('./ssh-g-config-resolution', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSshGConfigResolutionMocks( + await importOriginal() + ) +) describe('SshConnection', () => { beforeEach(() => { diff --git a/src/main/ssh/ssh-connection-file-transfers.ts b/src/main/ssh/ssh-connection-file-transfers.ts index 0382fa0495a..c15a0880586 100644 --- a/src/main/ssh/ssh-connection-file-transfers.ts +++ b/src/main/ssh/ssh-connection-file-transfers.ts @@ -12,12 +12,11 @@ import { import type { RemoteHostPlatform } from './ssh-remote-platform' import { downloadFileViaSystemSsh, - uploadDirectoryViaSystemSsh, uploadFileViaSystemSsh, - writeBufferViaSystemSsh, - writeFileViaSystemSsh, - type SystemSshBuildArgsOptions -} from './ssh-system-fallback' + writeBufferViaSystemSsh +} from './system-ssh-file-binary-transfer' +import { uploadDirectoryViaSystemSsh, writeFileViaSystemSsh } from './system-ssh-file-transfer' +import type { SystemSshBuildArgsOptions } from './system-ssh-args' export type SshRemoteFileOptions = { hostPlatform?: RemoteHostPlatform diff --git a/src/main/ssh/ssh-connection-github-probe.test.ts b/src/main/ssh/ssh-connection-github-probe.test.ts index f3b2a3253dd..e825fc28a28 100644 --- a/src/main/ssh/ssh-connection-github-probe.test.ts +++ b/src/main/ssh/ssh-connection-github-probe.test.ts @@ -7,14 +7,27 @@ import { createTarget } from './ssh-connection-test-fixtures' import { SshConnection } from './ssh-connection' -import { resolveWithSshG } from './ssh-config-parser' +import { resolveWithSshG } from './ssh-g-config-resolution' vi.mock('ssh2', async () => (await import('./ssh-connection-test-harness')).createSsh2Module()) vi.mock('./system-ssh-binary', async () => (await import('./ssh-connection-test-harness')).createSystemSshBinaryModule() ) -vi.mock('./ssh-system-fallback', async () => - (await import('./ssh-connection-test-harness')).createSystemFallbackModule() +vi.mock('./system-ssh-args', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshArgsMocks(await importOriginal()) +) +vi.mock('./system-ssh-command', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshCommandMocks(await importOriginal()) +) +vi.mock('./system-ssh-file-binary-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileBinaryTransferMocks( + await importOriginal() + ) +) +vi.mock('./system-ssh-file-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileTransferMocks( + await importOriginal() + ) ) vi.mock('./ssh-control-socket', async () => (await import('./ssh-connection-test-harness')).createControlSocketModule() @@ -22,6 +35,11 @@ vi.mock('./ssh-control-socket', async () => vi.mock('./ssh-config-parser', async () => (await import('./ssh-connection-test-harness')).createSshConfigParserModule() ) +vi.mock('./ssh-g-config-resolution', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSshGConfigResolutionMocks( + await importOriginal() + ) +) describe('SshConnection', () => { beforeEach(() => { diff --git a/src/main/ssh/ssh-connection-gssapi-fallback.test.ts b/src/main/ssh/ssh-connection-gssapi-fallback.test.ts index 8eeb6994a50..41a6e67fff0 100644 --- a/src/main/ssh/ssh-connection-gssapi-fallback.test.ts +++ b/src/main/ssh/ssh-connection-gssapi-fallback.test.ts @@ -17,14 +17,27 @@ import { createTarget } from './ssh-connection-test-fixtures' import { SshConnection } from './ssh-connection' -import { resolveWithSshG } from './ssh-config-parser' +import { resolveWithSshG } from './ssh-g-config-resolution' vi.mock('ssh2', async () => (await import('./ssh-connection-test-harness')).createSsh2Module()) vi.mock('./system-ssh-binary', async () => (await import('./ssh-connection-test-harness')).createSystemSshBinaryModule() ) -vi.mock('./ssh-system-fallback', async () => - (await import('./ssh-connection-test-harness')).createSystemFallbackModule() +vi.mock('./system-ssh-args', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshArgsMocks(await importOriginal()) +) +vi.mock('./system-ssh-command', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshCommandMocks(await importOriginal()) +) +vi.mock('./system-ssh-file-binary-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileBinaryTransferMocks( + await importOriginal() + ) +) +vi.mock('./system-ssh-file-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileTransferMocks( + await importOriginal() + ) ) vi.mock('./ssh-control-socket', async () => (await import('./ssh-connection-test-harness')).createControlSocketModule() @@ -32,6 +45,11 @@ vi.mock('./ssh-control-socket', async () => vi.mock('./ssh-config-parser', async () => (await import('./ssh-connection-test-harness')).createSshConfigParserModule() ) +vi.mock('./ssh-g-config-resolution', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSshGConfigResolutionMocks( + await importOriginal() + ) +) describe('SshConnection', () => { beforeEach(() => { diff --git a/src/main/ssh/ssh-connection-host-key-store-wiring.test.ts b/src/main/ssh/ssh-connection-host-key-store-wiring.test.ts index 68d3b6ec19a..5b3a834337f 100644 --- a/src/main/ssh/ssh-connection-host-key-store-wiring.test.ts +++ b/src/main/ssh/ssh-connection-host-key-store-wiring.test.ts @@ -15,7 +15,7 @@ import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:f import { tmpdir } from 'node:os' import { join } from 'node:path' // Type-only, so it is erased before vi.mock's hoisted factory runs. -import type * as SshConfigParser from './ssh-config-parser' +import type * as SshGConfigResolution from './ssh-g-config-resolution' import type * as Ssh2 from 'ssh2' const VALID_ED25519_HOST_KEY = Buffer.from( @@ -76,8 +76,8 @@ vi.mock('ssh2', async (importOriginal) => { } }) -vi.mock('./ssh-config-parser', async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('./ssh-g-config-resolution', async (importOriginal) => ({ + ...(await importOriginal()), resolveWithSshG: vi.fn(async () => null) })) diff --git a/src/main/ssh/ssh-connection-host-key-verification.test.ts b/src/main/ssh/ssh-connection-host-key-verification.test.ts index 4b0c10210f8..f8f82745d5f 100644 --- a/src/main/ssh/ssh-connection-host-key-verification.test.ts +++ b/src/main/ssh/ssh-connection-host-key-verification.test.ts @@ -10,8 +10,21 @@ vi.mock('ssh2', async () => (await import('./ssh-connection-test-harness')).crea vi.mock('./system-ssh-binary', async () => (await import('./ssh-connection-test-harness')).createSystemSshBinaryModule() ) -vi.mock('./ssh-system-fallback', async () => - (await import('./ssh-connection-test-harness')).createSystemFallbackModule() +vi.mock('./system-ssh-args', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshArgsMocks(await importOriginal()) +) +vi.mock('./system-ssh-command', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshCommandMocks(await importOriginal()) +) +vi.mock('./system-ssh-file-binary-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileBinaryTransferMocks( + await importOriginal() + ) +) +vi.mock('./system-ssh-file-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileTransferMocks( + await importOriginal() + ) ) vi.mock('./ssh-control-socket', async () => (await import('./ssh-connection-test-harness')).createControlSocketModule() @@ -19,6 +32,11 @@ vi.mock('./ssh-control-socket', async () => vi.mock('./ssh-config-parser', async () => (await import('./ssh-connection-test-harness')).createSshConfigParserModule() ) +vi.mock('./ssh-g-config-resolution', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSshGConfigResolutionMocks( + await importOriginal() + ) +) const UNIDENTIFIABLE_HOST_KEY = Buffer.from('not-a-real-host-key-blob') diff --git a/src/main/ssh/ssh-connection-manager-registry.test.ts b/src/main/ssh/ssh-connection-manager-registry.test.ts index 5832d1a08e4..5352a1ba890 100644 --- a/src/main/ssh/ssh-connection-manager-registry.test.ts +++ b/src/main/ssh/ssh-connection-manager-registry.test.ts @@ -7,8 +7,21 @@ vi.mock('ssh2', async () => (await import('./ssh-connection-test-harness')).crea vi.mock('./system-ssh-binary', async () => (await import('./ssh-connection-test-harness')).createSystemSshBinaryModule() ) -vi.mock('./ssh-system-fallback', async () => - (await import('./ssh-connection-test-harness')).createSystemFallbackModule() +vi.mock('./system-ssh-args', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshArgsMocks(await importOriginal()) +) +vi.mock('./system-ssh-command', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshCommandMocks(await importOriginal()) +) +vi.mock('./system-ssh-file-binary-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileBinaryTransferMocks( + await importOriginal() + ) +) +vi.mock('./system-ssh-file-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileTransferMocks( + await importOriginal() + ) ) vi.mock('./ssh-control-socket', async () => (await import('./ssh-connection-test-harness')).createControlSocketModule() @@ -16,6 +29,11 @@ vi.mock('./ssh-control-socket', async () => vi.mock('./ssh-config-parser', async () => (await import('./ssh-connection-test-harness')).createSshConfigParserModule() ) +vi.mock('./ssh-g-config-resolution', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSshGConfigResolutionMocks( + await importOriginal() + ) +) describe('SshConnectionManager', () => { beforeEach(() => { diff --git a/src/main/ssh/ssh-connection-manager-sign-in-reuse.test.ts b/src/main/ssh/ssh-connection-manager-sign-in-reuse.test.ts index 9ad909dd278..80fdc1a5b28 100644 --- a/src/main/ssh/ssh-connection-manager-sign-in-reuse.test.ts +++ b/src/main/ssh/ssh-connection-manager-sign-in-reuse.test.ts @@ -17,8 +17,21 @@ vi.mock('ssh2', async () => (await import('./ssh-connection-test-harness')).crea vi.mock('./system-ssh-binary', async () => (await import('./ssh-connection-test-harness')).createSystemSshBinaryModule() ) -vi.mock('./ssh-system-fallback', async () => - (await import('./ssh-connection-test-harness')).createSystemFallbackModule() +vi.mock('./system-ssh-args', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshArgsMocks(await importOriginal()) +) +vi.mock('./system-ssh-command', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshCommandMocks(await importOriginal()) +) +vi.mock('./system-ssh-file-binary-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileBinaryTransferMocks( + await importOriginal() + ) +) +vi.mock('./system-ssh-file-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileTransferMocks( + await importOriginal() + ) ) vi.mock('./ssh-control-socket', async () => (await import('./ssh-connection-test-harness')).createControlSocketModule() @@ -26,6 +39,11 @@ vi.mock('./ssh-control-socket', async () => vi.mock('./ssh-config-parser', async () => (await import('./ssh-connection-test-harness')).createSshConfigParserModule() ) +vi.mock('./ssh-g-config-resolution', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSshGConfigResolutionMocks( + await importOriginal() + ) +) const authFailure = (): Error => new Error('All configured authentication methods failed') diff --git a/src/main/ssh/ssh-connection-manager.ts b/src/main/ssh/ssh-connection-manager.ts index 3a5e2a4e50a..0b54330aae8 100644 --- a/src/main/ssh/ssh-connection-manager.ts +++ b/src/main/ssh/ssh-connection-manager.ts @@ -1,9 +1,6 @@ import type { SshTarget, SshConnectionState } from '../../shared/ssh-types' -import { - SshConnection, - type SshCachedCredentials, - type SshConnectionCallbacks -} from './ssh-connection' +import { SshConnection, type SshCachedCredentials } from './ssh-connection' +import type { SshConnectionCallbacks } from './ssh-connection-utils' import { recordSshConnectionOpened, recordSshConnectionReused } from './ssh-connection-attribution' import { createSshCredentialDeclinedError, isSshCredentialDeclined } from './ssh-credential-decline' import { createSshUserDisconnectedError, isSshUserDisconnected } from './ssh-user-disconnect' diff --git a/src/main/ssh/ssh-connection-reconnect-handshake-loss.test.ts b/src/main/ssh/ssh-connection-reconnect-handshake-loss.test.ts index 2b77c5ef10c..1477c08f650 100644 --- a/src/main/ssh/ssh-connection-reconnect-handshake-loss.test.ts +++ b/src/main/ssh/ssh-connection-reconnect-handshake-loss.test.ts @@ -15,8 +15,21 @@ vi.mock('ssh2', async () => (await import('./ssh-connection-test-harness')).crea vi.mock('./system-ssh-binary', async () => (await import('./ssh-connection-test-harness')).createSystemSshBinaryModule() ) -vi.mock('./ssh-system-fallback', async () => - (await import('./ssh-connection-test-harness')).createSystemFallbackModule() +vi.mock('./system-ssh-args', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshArgsMocks(await importOriginal()) +) +vi.mock('./system-ssh-command', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshCommandMocks(await importOriginal()) +) +vi.mock('./system-ssh-file-binary-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileBinaryTransferMocks( + await importOriginal() + ) +) +vi.mock('./system-ssh-file-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileTransferMocks( + await importOriginal() + ) ) vi.mock('./ssh-control-socket', async () => (await import('./ssh-connection-test-harness')).createControlSocketModule() @@ -24,6 +37,11 @@ vi.mock('./ssh-control-socket', async () => vi.mock('./ssh-config-parser', async () => (await import('./ssh-connection-test-harness')).createSshConfigParserModule() ) +vi.mock('./ssh-g-config-resolution', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSshGConfigResolutionMocks( + await importOriginal() + ) +) // ssh2's exact shape when TCP connects but the socket closes before the server banner. const preBannerLoss = (): Error => diff --git a/src/main/ssh/ssh-connection-reconnect-ladder.test.ts b/src/main/ssh/ssh-connection-reconnect-ladder.test.ts index 235a721d4a3..53acff12fd4 100644 --- a/src/main/ssh/ssh-connection-reconnect-ladder.test.ts +++ b/src/main/ssh/ssh-connection-reconnect-ladder.test.ts @@ -17,7 +17,7 @@ import { createTarget } from './ssh-connection-test-fixtures' import { SshConnection } from './ssh-connection' -import { resolveWithSshG } from './ssh-config-parser' +import { resolveWithSshG } from './ssh-g-config-resolution' import { CONNECT_TIMEOUT_MS, RECONNECT_BACKOFF_MS } from './ssh-connection-utils' import { MIN_SSH_RELAY_GRACE_PERIOD_SECONDS } from '../../shared/ssh-types' @@ -25,8 +25,21 @@ vi.mock('ssh2', async () => (await import('./ssh-connection-test-harness')).crea vi.mock('./system-ssh-binary', async () => (await import('./ssh-connection-test-harness')).createSystemSshBinaryModule() ) -vi.mock('./ssh-system-fallback', async () => - (await import('./ssh-connection-test-harness')).createSystemFallbackModule() +vi.mock('./system-ssh-args', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshArgsMocks(await importOriginal()) +) +vi.mock('./system-ssh-command', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshCommandMocks(await importOriginal()) +) +vi.mock('./system-ssh-file-binary-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileBinaryTransferMocks( + await importOriginal() + ) +) +vi.mock('./system-ssh-file-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileTransferMocks( + await importOriginal() + ) ) vi.mock('./ssh-control-socket', async () => (await import('./ssh-connection-test-harness')).createControlSocketModule() @@ -34,6 +47,11 @@ vi.mock('./ssh-control-socket', async () => vi.mock('./ssh-config-parser', async () => (await import('./ssh-connection-test-harness')).createSshConfigParserModule() ) +vi.mock('./ssh-g-config-resolution', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSshGConfigResolutionMocks( + await importOriginal() + ) +) describe('SshConnection', () => { beforeEach(() => { diff --git a/src/main/ssh/ssh-connection-sftp-namespace.test.ts b/src/main/ssh/ssh-connection-sftp-namespace.test.ts index dd568e4032c..59b48c3e50f 100644 --- a/src/main/ssh/ssh-connection-sftp-namespace.test.ts +++ b/src/main/ssh/ssh-connection-sftp-namespace.test.ts @@ -7,6 +7,10 @@ import { mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import type * as SystemSshArgs from './system-ssh-args' +import type * as SystemSshFileBinaryTransfer from './system-ssh-file-binary-transfer' +import type * as SystemSshFileTransfer from './system-ssh-file-transfer' +import type * as SshGConfigResolution from './ssh-g-config-resolution' vi.mock('ssh2', () => ({ BaseAgent: class {}, @@ -15,25 +19,34 @@ vi.mock('ssh2', () => ({ utils: { parseKey: vi.fn() } })) -vi.mock('./ssh-system-fallback', () => ({ - getOrcaControlSocketPath: vi.fn().mockReturnValue(null), - spawnSystemSsh: vi.fn(), - spawnSystemSshCommand: vi.fn(), +vi.mock('./system-ssh-args', async (importOriginal) => ({ + ...(await importOriginal()), + getOrcaControlSocketPath: vi.fn().mockReturnValue(null) +})) +vi.mock('./system-ssh-command', () => ({ spawnSystemSsh: vi.fn(), spawnSystemSshCommand: vi.fn() })) +vi.mock('./system-ssh-file-binary-transfer', async (importOriginal) => ({ + ...(await importOriginal()), downloadFileViaSystemSsh: vi.fn().mockResolvedValue(undefined), - uploadDirectoryViaSystemSsh: vi.fn().mockResolvedValue(undefined), uploadFileViaSystemSsh: vi.fn().mockResolvedValue(undefined), - writeBufferViaSystemSsh: vi.fn().mockResolvedValue(undefined), + writeBufferViaSystemSsh: vi.fn().mockResolvedValue(undefined) +})) +vi.mock('./system-ssh-file-transfer', async (importOriginal) => ({ + ...(await importOriginal()), + uploadDirectoryViaSystemSsh: vi.fn().mockResolvedValue(undefined), writeFileViaSystemSsh: vi.fn().mockResolvedValue(undefined) })) vi.mock('./ssh-control-socket', () => ({ removeControlSocketPath: vi.fn() })) -vi.mock('./ssh-config-parser', () => ({ resolveWithSshG: vi.fn().mockResolvedValue(null) })) +vi.mock('./ssh-g-config-resolution', async (importOriginal) => ({ + ...(await importOriginal()), + resolveWithSshG: vi.fn().mockResolvedValue(null) +})) import { SshConnection } from './ssh-connection' import type { SftpNamespacePathMapping } from './sftp-namespace-resolution' import { getRemoteHostPlatform } from './ssh-remote-platform' import { isSshSessionLimitError } from './ssh-session-limit-error' -import { uploadDirectoryViaSystemSsh, writeFileViaSystemSsh } from './ssh-system-fallback' +import { uploadDirectoryViaSystemSsh, writeFileViaSystemSsh } from './system-ssh-file-transfer' import type { SshTarget } from '../../shared/ssh-types' const SHELL_HOME = '/var/services/homes/alice' diff --git a/src/main/ssh/ssh-connection-system-transport.test.ts b/src/main/ssh/ssh-connection-system-transport.test.ts index 32e822e37f6..461a541d39f 100644 --- a/src/main/ssh/ssh-connection-system-transport.test.ts +++ b/src/main/ssh/ssh-connection-system-transport.test.ts @@ -19,9 +19,10 @@ import { createSystemCommandChannel, createTarget } from './ssh-connection-test-fixtures' -import { SshConnection, shouldUseSystemSshTransport } from './ssh-connection' -import { resolveWithSshG } from './ssh-config-parser' -import { writeFileViaSystemSsh } from './ssh-system-fallback' +import { SshConnection } from './ssh-connection' +import { shouldUseSystemSshTransport } from './ssh-transport-selection' +import { resolveWithSshG } from './ssh-g-config-resolution' +import { writeFileViaSystemSsh } from './system-ssh-file-transfer' import { CONNECT_TIMEOUT_MS } from './ssh-connection-utils' import type { SshTarget } from '../../shared/ssh-types' import { @@ -33,8 +34,21 @@ vi.mock('ssh2', async () => (await import('./ssh-connection-test-harness')).crea vi.mock('./system-ssh-binary', async () => (await import('./ssh-connection-test-harness')).createSystemSshBinaryModule() ) -vi.mock('./ssh-system-fallback', async () => - (await import('./ssh-connection-test-harness')).createSystemFallbackModule() +vi.mock('./system-ssh-args', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshArgsMocks(await importOriginal()) +) +vi.mock('./system-ssh-command', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshCommandMocks(await importOriginal()) +) +vi.mock('./system-ssh-file-binary-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileBinaryTransferMocks( + await importOriginal() + ) +) +vi.mock('./system-ssh-file-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileTransferMocks( + await importOriginal() + ) ) vi.mock('./ssh-control-socket', async () => (await import('./ssh-connection-test-harness')).createControlSocketModule() @@ -42,6 +56,11 @@ vi.mock('./ssh-control-socket', async () => vi.mock('./ssh-config-parser', async () => (await import('./ssh-connection-test-harness')).createSshConfigParserModule() ) +vi.mock('./ssh-g-config-resolution', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSshGConfigResolutionMocks( + await importOriginal() + ) +) describe('SshConnection', () => { beforeEach(() => { diff --git a/src/main/ssh/ssh-connection-test-fixtures.ts b/src/main/ssh/ssh-connection-test-fixtures.ts index c15a787b85d..45de1425340 100644 --- a/src/main/ssh/ssh-connection-test-fixtures.ts +++ b/src/main/ssh/ssh-connection-test-fixtures.ts @@ -2,8 +2,8 @@ import { EventEmitter } from 'node:events' import { vi } from 'vitest' import type { Mock } from 'vitest' import type { AnyAuthMethod, AuthenticationType } from 'ssh2' -import type { SshConnectionCallbacks } from './ssh-connection' -import type { SshResolvedConfig } from './ssh-config-parser' +import type { SshConnectionCallbacks } from './ssh-connection-utils' +import type { SshResolvedConfig } from './ssh-g-config-resolution' import type { SshTarget } from '../../shared/ssh-types' export function createTarget(overrides?: Partial): SshTarget { diff --git a/src/main/ssh/ssh-connection-test-harness.ts b/src/main/ssh/ssh-connection-test-harness.ts index 5c4567598e5..1ad441fd11d 100644 --- a/src/main/ssh/ssh-connection-test-harness.ts +++ b/src/main/ssh/ssh-connection-test-harness.ts @@ -2,9 +2,14 @@ import { vi } from 'vitest' import { createSystemCommandChannel, createSystemSshProcess } from './ssh-connection-test-fixtures' import type { SshConnection } from './ssh-connection' import type { MockSystemCommandChannel, MockSystemSshProcess } from './ssh-connection-test-fixtures' -import type { SshResolvedConfig } from './ssh-config-parser' +import type { SshResolvedConfig } from './ssh-g-config-resolution' import type { SystemSshBuildArgsOptions } from './system-ssh-args' import type { SshTarget } from '../../shared/ssh-types' +import type * as SystemSshArgs from './system-ssh-args' +import type * as SystemSshCommand from './system-ssh-command' +import type * as SystemSshFileBinaryTransfer from './system-ssh-file-binary-transfer' +import type * as SystemSshFileTransfer from './system-ssh-file-transfer' +import type * as SshGConfigResolution from './ssh-g-config-resolution' import { resetSsh2ClientState, ssh2Mock } from './__tests__/ssh-connection-test-client' export { clientInstances, @@ -22,23 +27,10 @@ export type { MockSshClient, Ssh2ModuleMock } from './__tests__/ssh-connection-t export type SystemSshBinaryModuleMock = { findSystemSsh: typeof findSystemSshMock } -export type SystemFallbackModuleMock = { - getOrcaControlSocketPath: typeof getOrcaControlSocketPathMock - spawnSystemSsh: typeof spawnSystemSshMock - spawnSystemSshCommand: typeof spawnSystemSshCommandMock - downloadFileViaSystemSsh: typeof downloadFileViaSystemSshMock - uploadDirectoryViaSystemSsh: typeof uploadDirectoryViaSystemSshMock - uploadFileViaSystemSsh: typeof uploadFileViaSystemSshMock - writeBufferViaSystemSsh: typeof writeBufferViaSystemSshMock - writeFileViaSystemSsh: typeof writeFileViaSystemSshMock -} - export type ControlSocketModuleMock = { removeControlSocketPath: typeof removeControlSocketPathMock } -export type SshConfigParserModuleMock = { resolveWithSshG: typeof resolveWithSshGMock } - export const findSystemSshMock = vi.fn<() => string | null>() export const getOrcaControlSocketPathMock = vi.fn<(target: SshTarget, options?: SystemSshBuildArgsOptions) => string | null>() @@ -83,15 +75,32 @@ export function createSystemSshBinaryModule(): SystemSshBinaryModuleMock { return { findSystemSsh: findSystemSshMock } } -export function createSystemFallbackModule(): SystemFallbackModuleMock { +// The system-ssh seams SshConnection reaches, each layered over its module's real exports. +export function withSystemSshArgsMocks(actual: typeof SystemSshArgs) { + return { ...actual, getOrcaControlSocketPath: getOrcaControlSocketPathMock } +} + +export function withSystemSshCommandMocks(actual: typeof SystemSshCommand) { return { - getOrcaControlSocketPath: getOrcaControlSocketPathMock, + ...actual, spawnSystemSsh: spawnSystemSshMock, - spawnSystemSshCommand: spawnSystemSshCommandMock, + spawnSystemSshCommand: spawnSystemSshCommandMock + } +} + +export function withSystemSshFileBinaryTransferMocks(actual: typeof SystemSshFileBinaryTransfer) { + return { + ...actual, downloadFileViaSystemSsh: downloadFileViaSystemSshMock, - uploadDirectoryViaSystemSsh: uploadDirectoryViaSystemSshMock, uploadFileViaSystemSsh: uploadFileViaSystemSshMock, - writeBufferViaSystemSsh: writeBufferViaSystemSshMock, + writeBufferViaSystemSsh: writeBufferViaSystemSshMock + } +} + +export function withSystemSshFileTransferMocks(actual: typeof SystemSshFileTransfer) { + return { + ...actual, + uploadDirectoryViaSystemSsh: uploadDirectoryViaSystemSshMock, writeFileViaSystemSsh: writeFileViaSystemSshMock } } @@ -100,8 +109,13 @@ export function createControlSocketModule(): ControlSocketModuleMock { return { removeControlSocketPath: removeControlSocketPathMock } } -export function createSshConfigParserModule(): SshConfigParserModuleMock { - return { resolveWithSshG: resolveWithSshGMock } +// Why: an unreadable config keeps the developer's own ~/.ssh/config out of alias-claim verdicts. +export function createSshConfigParserModule(): Record { + return {} +} + +export function withSshGConfigResolutionMocks(actual: typeof SshGConfigResolution) { + return { ...actual, resolveWithSshG: resolveWithSshGMock } } export function resetSshConnectionMocks(): void { diff --git a/src/main/ssh/ssh-connection-utils.test.ts b/src/main/ssh/ssh-connection-utils.test.ts index d2e7094f74b..bd1ea5fbf7e 100644 --- a/src/main/ssh/ssh-connection-utils.test.ts +++ b/src/main/ssh/ssh-connection-utils.test.ts @@ -32,7 +32,7 @@ import { findDefaultKeyFile, resolveAgentSocket } from './ssh-auth-resolution' import { resolveEffectiveProxy } from './ssh-proxy-command' import { walkInitialAuthLadder } from './ssh-connection-test-fixtures' import type { SshTarget } from '../../shared/ssh-types' -import type { SshResolvedConfig } from './ssh-config-parser' +import type { SshResolvedConfig } from './ssh-g-config-resolution' // ── isTransientError ───────────────────────────────────────────────── diff --git a/src/main/ssh/ssh-connection-utils.ts b/src/main/ssh/ssh-connection-utils.ts index 7ae9ac5459f..8a95b54c57c 100644 --- a/src/main/ssh/ssh-connection-utils.ts +++ b/src/main/ssh/ssh-connection-utils.ts @@ -1,6 +1,6 @@ import type { ConnectConfig } from 'ssh2' import type { SshTarget, SshConnectionState } from '../../shared/ssh-types' -import type { SshResolvedConfig } from './ssh-config-parser' +import type { SshResolvedConfig } from './ssh-g-config-resolution' import { findEncryptedPrivateKeyPath, isUnencryptedPrivateKey, diff --git a/src/main/ssh/ssh-connection-wake-retry.test.ts b/src/main/ssh/ssh-connection-wake-retry.test.ts index db5a6cbfd9c..afcc34d74d2 100644 --- a/src/main/ssh/ssh-connection-wake-retry.test.ts +++ b/src/main/ssh/ssh-connection-wake-retry.test.ts @@ -16,7 +16,7 @@ import { createTarget } from './ssh-connection-test-fixtures' import { SshConnection } from './ssh-connection' -import { resolveWithSshG } from './ssh-config-parser' +import { resolveWithSshG } from './ssh-g-config-resolution' import { SshConnectionManager } from './ssh-connection-manager' import { RECONNECT_BACKOFF_MS } from './ssh-connection-utils' import { recordSshUserDisconnected, resetSshUserDisconnectsForTests } from './ssh-user-disconnect' @@ -25,8 +25,21 @@ vi.mock('ssh2', async () => (await import('./ssh-connection-test-harness')).crea vi.mock('./system-ssh-binary', async () => (await import('./ssh-connection-test-harness')).createSystemSshBinaryModule() ) -vi.mock('./ssh-system-fallback', async () => - (await import('./ssh-connection-test-harness')).createSystemFallbackModule() +vi.mock('./system-ssh-args', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshArgsMocks(await importOriginal()) +) +vi.mock('./system-ssh-command', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshCommandMocks(await importOriginal()) +) +vi.mock('./system-ssh-file-binary-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileBinaryTransferMocks( + await importOriginal() + ) +) +vi.mock('./system-ssh-file-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileTransferMocks( + await importOriginal() + ) ) vi.mock('./ssh-control-socket', async () => (await import('./ssh-connection-test-harness')).createControlSocketModule() @@ -34,6 +47,11 @@ vi.mock('./ssh-control-socket', async () => vi.mock('./ssh-config-parser', async () => (await import('./ssh-connection-test-harness')).createSshConfigParserModule() ) +vi.mock('./ssh-g-config-resolution', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSshGConfigResolutionMocks( + await importOriginal() + ) +) function failHandshakes(): void { ssh2Mock.connectBehavior = 'error' diff --git a/src/main/ssh/ssh-connection.test.ts b/src/main/ssh/ssh-connection.test.ts index 5535c5e7e8e..1239f45c182 100644 --- a/src/main/ssh/ssh-connection.test.ts +++ b/src/main/ssh/ssh-connection.test.ts @@ -15,8 +15,21 @@ vi.mock('ssh2', async () => (await import('./ssh-connection-test-harness')).crea vi.mock('./system-ssh-binary', async () => (await import('./ssh-connection-test-harness')).createSystemSshBinaryModule() ) -vi.mock('./ssh-system-fallback', async () => - (await import('./ssh-connection-test-harness')).createSystemFallbackModule() +vi.mock('./system-ssh-args', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshArgsMocks(await importOriginal()) +) +vi.mock('./system-ssh-command', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshCommandMocks(await importOriginal()) +) +vi.mock('./system-ssh-file-binary-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileBinaryTransferMocks( + await importOriginal() + ) +) +vi.mock('./system-ssh-file-transfer', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSystemSshFileTransferMocks( + await importOriginal() + ) ) vi.mock('./ssh-control-socket', async () => (await import('./ssh-connection-test-harness')).createControlSocketModule() @@ -24,6 +37,11 @@ vi.mock('./ssh-control-socket', async () => vi.mock('./ssh-config-parser', async () => (await import('./ssh-connection-test-harness')).createSshConfigParserModule() ) +vi.mock('./ssh-g-config-resolution', async (importOriginal) => + (await import('./ssh-connection-test-harness')).withSshGConfigResolutionMocks( + await importOriginal() + ) +) describe('SshConnection', () => { beforeEach(() => { diff --git a/src/main/ssh/ssh-connection.ts b/src/main/ssh/ssh-connection.ts index ec1f331d03b..d17ca46b8ed 100644 --- a/src/main/ssh/ssh-connection.ts +++ b/src/main/ssh/ssh-connection.ts @@ -23,12 +23,11 @@ import type { import type { SshTarget, SshConnectionState, SshConnectionStatus } from '../../shared/ssh-types' import { getOrcaControlSocketPath, - spawnSystemSshCommand, + isOpenSshConfigBackedTarget, type SystemSshBuildArgsOptions -} from './ssh-system-fallback' -import { resolveWithSshG, type SshResolvedConfig } from './ssh-config-parser' +} from './system-ssh-args' +import { spawnSystemSshCommand } from './system-ssh-command' import { removeControlSocketPath } from './ssh-control-socket' -import { isOpenSshConfigBackedTarget } from './system-ssh-args' import { INITIAL_RETRY_ATTEMPTS, INITIAL_RETRY_DELAY_MS, @@ -57,7 +56,12 @@ import { orderServerHostKeyAlgorithms } from './ssh-host-key-verifier' import { HostKeyVerificationError, isHostKeyVerificationError } from './ssh-host-key-decision' -import { siteConfigMayRestrictHostKeys, sshGArgsForHost } from './ssh-g-config-resolution' +import { + resolveWithSshG, + siteConfigMayRestrictHostKeys, + sshGArgsForHost, + type SshResolvedConfig +} from './ssh-g-config-resolution' import { boundSshHostKeyStoreFile, loadTrustedHostKeys, @@ -95,7 +99,6 @@ import { tagSftpHandshakeCorruption } from './sftp-handshake-corruption' import { withTimeout } from '../../shared/promise-timeout-fallback' import { isEphemeralRuntimeSshOwner } from '../../shared/managed-orcad-ssh-owner' import { isSshUserDisconnected } from './ssh-user-disconnect' -export type { SshConnectionCallbacks } from './ssh-connection-utils' /** The sign-in a connection holds in memory, handed to the connection that replaces it. */ export type SshCachedCredentials = { password: string | null; passphrase: string | null } @@ -1611,5 +1614,3 @@ export class SshConnection { this.callbacks.onStateChange(this.target.id, { ...this.state }) } } - -export { shouldUseSystemSshTransport } from './ssh-transport-selection' diff --git a/src/main/ssh/ssh-control-socket.ts b/src/main/ssh/ssh-control-socket.ts index 73622ba504e..4020a9459f8 100644 --- a/src/main/ssh/ssh-control-socket.ts +++ b/src/main/ssh/ssh-control-socket.ts @@ -3,7 +3,7 @@ import { lstatSync, mkdirSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { isAbsolute, join as pathJoin } from 'node:path' import type { SshTarget } from '../../shared/ssh-types' -import type { SshResolvedConfig } from './ssh-config-parser' +import type { SshResolvedConfig } from './ssh-g-config-resolution' export type SystemSshResolvedConfig = Pick< SshResolvedConfig, diff --git a/src/main/ssh/ssh-credential-prompt-login-grace-wire.test.ts b/src/main/ssh/ssh-credential-prompt-login-grace-wire.test.ts index 850a56ad531..7a6dfff5ba9 100644 --- a/src/main/ssh/ssh-credential-prompt-login-grace-wire.test.ts +++ b/src/main/ssh/ssh-credential-prompt-login-grace-wire.test.ts @@ -7,11 +7,11 @@ import { SshConnection } from './ssh-connection' import { INITIAL_RETRY_DELAY_MS } from './ssh-connection-utils' import { createCallbacks, createResolvedConfig, createTarget } from './ssh-connection-test-fixtures' import { initSshHostKeyStoreFile } from './ssh-host-key-store' -import { resolveWithSshG } from './ssh-config-parser' -import type * as SshConfigParser from './ssh-config-parser' +import { resolveWithSshG } from './ssh-g-config-resolution' +import type * as SshGConfigResolution from './ssh-g-config-resolution' -vi.mock('./ssh-config-parser', async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('./ssh-g-config-resolution', async (importOriginal) => ({ + ...(await importOriginal()), resolveWithSshG: vi.fn() })) diff --git a/src/main/ssh/ssh-host-relay-windows-inventory.ts b/src/main/ssh/ssh-host-relay-windows-inventory.ts index 7b5fe70094d..c83cd01405e 100644 --- a/src/main/ssh/ssh-host-relay-windows-inventory.ts +++ b/src/main/ssh/ssh-host-relay-windows-inventory.ts @@ -9,7 +9,10 @@ */ import { RELAY_REMOTE_DIR } from './relay-protocol' import { commandWithNodePath } from './ssh-remote-commands' -import { powerShellLiteral, powerShellNativeArg } from './ssh-remote-powershell' +import { + quotePowerShellLiteral as powerShellLiteral, + quotePowerShellNativeArgument as powerShellNativeArg +} from '../../shared/powershell-native-argument' import { windowsRelayPipePathsForSocketName } from './ssh-relay-endpoints' import { RELAY_INSTALL_MODEL, remoteInstallVersionDirRegex } from './remote-install-model' import { joinRemotePath, type RemoteHostPlatform } from './ssh-remote-platform' diff --git a/src/main/ssh/ssh-keyboard-interactive-wire.test.ts b/src/main/ssh/ssh-keyboard-interactive-wire.test.ts index 5f45f27a52d..77f74e90c9c 100644 --- a/src/main/ssh/ssh-keyboard-interactive-wire.test.ts +++ b/src/main/ssh/ssh-keyboard-interactive-wire.test.ts @@ -6,11 +6,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { SshConnection } from './ssh-connection' import { createCallbacks, createResolvedConfig, createTarget } from './ssh-connection-test-fixtures' import { initSshHostKeyStoreFile } from './ssh-host-key-store' -import { resolveWithSshG } from './ssh-config-parser' -import type * as SshConfigParser from './ssh-config-parser' +import { resolveWithSshG } from './ssh-g-config-resolution' +import type * as SshGConfigResolution from './ssh-g-config-resolution' -vi.mock('./ssh-config-parser', async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('./ssh-g-config-resolution', async (importOriginal) => ({ + ...(await importOriginal()), resolveWithSshG: vi.fn() })) diff --git a/src/main/ssh/ssh-multi-factor-authentication.test.ts b/src/main/ssh/ssh-multi-factor-authentication.test.ts index c6c53135711..694ee8b21dc 100644 --- a/src/main/ssh/ssh-multi-factor-authentication.test.ts +++ b/src/main/ssh/ssh-multi-factor-authentication.test.ts @@ -18,7 +18,7 @@ import { } from 'ssh2' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { SshTarget } from '../../shared/ssh-types' -import type { SshResolvedConfig } from './ssh-config-parser' +import type { SshResolvedConfig } from './ssh-g-config-resolution' import { buildConnectConfig, type BuildConnectConfigOptions } from './ssh-connection-utils' vi.mock('node:os', async (importOriginal) => { diff --git a/src/main/ssh/ssh-multi-key-authentication.test.ts b/src/main/ssh/ssh-multi-key-authentication.test.ts index ea069965be1..7badbfb5cd2 100644 --- a/src/main/ssh/ssh-multi-key-authentication.test.ts +++ b/src/main/ssh/ssh-multi-key-authentication.test.ts @@ -27,7 +27,7 @@ import { getPassphrasePrivateKeyPath } from './ssh-private-key-authentication' import { buildSshArgs } from './system-ssh-args' import { walkInitialAuthLadder } from './ssh-connection-test-fixtures' import type { SshTarget } from '../../shared/ssh-types' -import type { SshResolvedConfig } from './ssh-config-parser' +import type { SshResolvedConfig } from './ssh-g-config-resolution' function makeTarget(overrides: Partial = {}): SshTarget { return { diff --git a/src/main/ssh/ssh-proxy-command.ts b/src/main/ssh/ssh-proxy-command.ts index d4e8c33a1f9..a597ab5b55a 100644 --- a/src/main/ssh/ssh-proxy-command.ts +++ b/src/main/ssh/ssh-proxy-command.ts @@ -3,7 +3,7 @@ import { spawnProcess } from '@orca/process-host' import { Duplex } from 'node:stream' import type { Socket as NetSocket } from 'node:net' import type { SshTarget } from '../../shared/ssh-types' -import type { SshResolvedConfig } from './ssh-config-parser' +import type { SshResolvedConfig } from './ssh-g-config-resolution' import { shellEscape } from './ssh-connection-utils' import { isOpenSshConfigBackedTarget } from './system-ssh-args' diff --git a/src/main/ssh/ssh-relay-gc-claim.ts b/src/main/ssh/ssh-relay-gc-claim.ts index 568732d3ec9..aa71589c7f5 100644 --- a/src/main/ssh/ssh-relay-gc-claim.ts +++ b/src/main/ssh/ssh-relay-gc-claim.ts @@ -9,7 +9,8 @@ import { tryStealInstallLockCommand } from './ssh-relay-install-lock-commands' import { removeRemoteTreeCommand } from './ssh-remote-commands' -import { powerShellCommand, powerShellLiteral } from './ssh-remote-powershell' +import { powerShellCommand } from './ssh-remote-powershell' +import { quotePowerShellLiteral as powerShellLiteral } from '../../shared/powershell-native-argument' import { getRemoteHostPlatform, isWindowsRemoteHost, diff --git a/src/main/ssh/ssh-relay-install-lock-commands.ts b/src/main/ssh/ssh-relay-install-lock-commands.ts index c3a55fb7724..cd8501e05b5 100644 --- a/src/main/ssh/ssh-relay-install-lock-commands.ts +++ b/src/main/ssh/ssh-relay-install-lock-commands.ts @@ -1,5 +1,6 @@ import { shellEscape } from './ssh-connection-utils' -import { powerShellCommand, powerShellLiteral } from './ssh-remote-powershell' +import { powerShellCommand } from './ssh-remote-powershell' +import { quotePowerShellLiteral as powerShellLiteral } from '../../shared/powershell-native-argument' import { isWindowsRemoteHost, type RemoteHostPlatform } from './ssh-remote-platform' import * as exitedOwnerSteal from './ssh-relay-install-lock-exited-owner' diff --git a/src/main/ssh/ssh-relay-install-lock-exited-owner.ts b/src/main/ssh/ssh-relay-install-lock-exited-owner.ts index c09e7184d57..4a425c3ad97 100644 --- a/src/main/ssh/ssh-relay-install-lock-exited-owner.ts +++ b/src/main/ssh/ssh-relay-install-lock-exited-owner.ts @@ -4,7 +4,7 @@ */ import { shellEscape } from './ssh-connection-utils' import type { InstallLockOwnerFile } from './ssh-relay-install-lock-commands' -import { powerShellLiteral } from './ssh-remote-powershell' +import { quotePowerShellLiteral as powerShellLiteral } from '../../shared/powershell-native-argument' import { posixStateMutationGroupRecord, posixStateMutationPidRecord diff --git a/src/main/ssh/ssh-relay-windows-launch-command.ts b/src/main/ssh/ssh-relay-windows-launch-command.ts index 1d8a858c84d..517204c0d1c 100644 --- a/src/main/ssh/ssh-relay-windows-launch-command.ts +++ b/src/main/ssh/ssh-relay-windows-launch-command.ts @@ -1,7 +1,7 @@ /** How the host census reaches a running Windows relay through its own bridge. */ import { commandWithNodePath } from './ssh-remote-commands' import type { RemoteHostPlatform } from './ssh-remote-platform' -import { powerShellLiteral } from './ssh-remote-powershell' +import { quotePowerShellLiteral as powerShellLiteral } from '../../shared/powershell-native-argument' export function windowsRelayConnectCommand( hostPlatform: RemoteHostPlatform, diff --git a/src/main/ssh/ssh-remote-commands.ts b/src/main/ssh/ssh-remote-commands.ts index f5a01f2c3c3..f96b29987f6 100644 --- a/src/main/ssh/ssh-remote-commands.ts +++ b/src/main/ssh/ssh-remote-commands.ts @@ -1,7 +1,8 @@ import { remoteInstallListingRegexSource, type RemoteInstallModel } from './remote-install-model' import type { RemoteHostPlatform } from './ssh-remote-platform' import { isWindowsRemoteHost, joinRemotePath, remoteDirname } from './ssh-remote-platform' -import { powerShellCommand, powerShellLiteral } from './ssh-remote-powershell' +import { powerShellCommand } from './ssh-remote-powershell' +import { quotePowerShellLiteral as powerShellLiteral } from '../../shared/powershell-native-argument' import { shellEscape } from './ssh-connection-utils' export function readRemoteHomeCommand(host: RemoteHostPlatform): string { diff --git a/src/main/ssh/ssh-remote-directory-browse.ts b/src/main/ssh/ssh-remote-directory-browse.ts index 25f1699c216..66461c60b46 100644 --- a/src/main/ssh/ssh-remote-directory-browse.ts +++ b/src/main/ssh/ssh-remote-directory-browse.ts @@ -1,6 +1,7 @@ import type { SshConnectionManager } from './ssh-connection-manager' import type { SshExecOptions } from './ssh-connection-utils' -import { powerShellCommand, powerShellLiteral } from './ssh-remote-powershell' +import { powerShellCommand } from './ssh-remote-powershell' +import { quotePowerShellLiteral as powerShellLiteral } from '../../shared/powershell-native-argument' import type { FilesystemPathFlavor } from '../../shared/filesystem-entry-types' import { sortDirEntries } from '../../shared/file-name-sort' import { buildPosixStdoutFence } from '../../shared/posix-stdout-fence' diff --git a/src/main/ssh/ssh-remote-node-toolchain-probe.ts b/src/main/ssh/ssh-remote-node-toolchain-probe.ts index be658996137..1753165dd9b 100644 --- a/src/main/ssh/ssh-remote-node-toolchain-probe.ts +++ b/src/main/ssh/ssh-remote-node-toolchain-probe.ts @@ -1,6 +1,6 @@ import { posix as posixPath } from 'node:path' import { shellEscape } from './ssh-connection-utils' -import { powerShellLiteral } from './ssh-remote-powershell' +import { quotePowerShellLiteral as powerShellLiteral } from '../../shared/powershell-native-argument' /** The host-Node floor for the npm path. */ export const MIN_HOST_NODE_MAJOR = 18 diff --git a/src/main/ssh/ssh-remote-powershell.ts b/src/main/ssh/ssh-remote-powershell.ts index 31587bcc668..772ea2de65c 100644 --- a/src/main/ssh/ssh-remote-powershell.ts +++ b/src/main/ssh/ssh-remote-powershell.ts @@ -1,10 +1,6 @@ import { gunzipSync, gzipSync } from 'node:zlib' import { encodePowerShellCommand } from '../../shared/powershell-command-encoding' import { CMD_EXE_COMMAND_LINE_MAX_CHARS } from '../providers/windows-shell-args' -export { - quotePowerShellLiteral as powerShellLiteral, - quotePowerShellNativeArgument as powerShellNativeArg -} from '../../shared/powershell-native-argument' // Why cmd.exe and not the 32767 CreateProcess cap: Windows OpenSSH runs every exec request // through sshd's DefaultShell, cmd.exe on a stock install. Budget under cmd.exe's own ceiling diff --git a/src/main/ssh/ssh-system-fallback.ts b/src/main/ssh/ssh-system-fallback.ts deleted file mode 100644 index b34a7579a1f..00000000000 --- a/src/main/ssh/ssh-system-fallback.ts +++ /dev/null @@ -1,13 +0,0 @@ -export { findSystemSsh } from './system-ssh-binary' -export { - buildSshArgs, - getOrcaControlSocketPath, - type SystemSshBuildArgsOptions -} from './system-ssh-args' -export { spawnSystemSsh, spawnSystemSshCommand, type SystemSshProcess } from './system-ssh-command' -export { - downloadFileViaSystemSsh, - uploadFileViaSystemSsh, - writeBufferViaSystemSsh -} from './system-ssh-file-binary-transfer' -export { uploadDirectoryViaSystemSsh, writeFileViaSystemSsh } from './system-ssh-file-transfer' diff --git a/src/main/ssh/ssh-system-transport.integration.test.ts b/src/main/ssh/ssh-system-transport.integration.test.ts index 645c8af38c7..81d1daa6439 100644 --- a/src/main/ssh/ssh-system-transport.integration.test.ts +++ b/src/main/ssh/ssh-system-transport.integration.test.ts @@ -7,7 +7,7 @@ vi.mock('electron', () => ({ })) import { SshConnection } from './ssh-connection' -import { uploadDirectoryViaSystemSsh } from './ssh-system-fallback' +import { uploadDirectoryViaSystemSsh } from './system-ssh-file-transfer' import type { SshTarget } from '../../shared/ssh-types' import { relayArtifactFilenames } from './relay-install-artifacts' diff --git a/src/main/ssh/ssh-transport-selection.ts b/src/main/ssh/ssh-transport-selection.ts index 2c058fcac85..ffa311eb4d6 100644 --- a/src/main/ssh/ssh-transport-selection.ts +++ b/src/main/ssh/ssh-transport-selection.ts @@ -1,7 +1,7 @@ import { constants } from 'node:fs' import { open, stat } from 'node:fs/promises' import type { SshTarget } from '../../shared/ssh-types' -import type { SshResolvedConfig } from './ssh-config-parser' +import type { SshResolvedConfig } from './ssh-g-config-resolution' import { listDefaultIdentityFilePaths, resolveIdentityFilePaths } from './ssh-auth-resolution' import { resolveSshConfigHomePath } from './ssh-config-path-expansion' import { diff --git a/src/main/ssh/system-ssh-dynamic-forward-process.test.ts b/src/main/ssh/system-ssh-dynamic-forward-process.test.ts index 51ee93af959..2a4eacabcc3 100644 --- a/src/main/ssh/system-ssh-dynamic-forward-process.test.ts +++ b/src/main/ssh/system-ssh-dynamic-forward-process.test.ts @@ -10,10 +10,7 @@ const { spawnMock, connectMock, createServerMock, waitForStopMock } = vi.hoisted vi.mock('node:child_process', () => ({ spawn: spawnMock })) vi.mock('node:net', () => ({ connect: connectMock, createServer: createServerMock })) -vi.mock('./ssh-system-fallback', async (importOriginal) => { - const actual = (await importOriginal()) as Record - return { ...actual, findSystemSsh: () => '/usr/bin/ssh' } -}) +vi.mock('./system-ssh-binary', () => ({ findSystemSsh: () => '/usr/bin/ssh' })) vi.mock('./system-ssh-forward-process', () => ({ waitForSystemSshForwardStop: waitForStopMock })) diff --git a/src/main/ssh/system-ssh-dynamic-forward-process.ts b/src/main/ssh/system-ssh-dynamic-forward-process.ts index bd39a043275..e036dacd927 100644 --- a/src/main/ssh/system-ssh-dynamic-forward-process.ts +++ b/src/main/ssh/system-ssh-dynamic-forward-process.ts @@ -1,7 +1,8 @@ import { spawn, type ChildProcess } from 'node:child_process' import { connect, createServer, type AddressInfo, type Socket } from 'node:net' import type { SshTarget } from '../../shared/ssh-types' -import { buildSshArgs, findSystemSsh, type SystemSshBuildArgsOptions } from './ssh-system-fallback' +import { buildSshArgs, type SystemSshBuildArgsOptions } from './system-ssh-args' +import { findSystemSsh } from './system-ssh-binary' import { waitForSystemSshForwardStop } from './system-ssh-forward-process' const STARTUP_TIMEOUT_MS = 10_000 diff --git a/src/main/ssh/system-ssh-file-binary-transfer.ts b/src/main/ssh/system-ssh-file-binary-transfer.ts index 662d1548231..8ab036563c9 100644 --- a/src/main/ssh/system-ssh-file-binary-transfer.ts +++ b/src/main/ssh/system-ssh-file-binary-transfer.ts @@ -12,7 +12,8 @@ import { } from './system-ssh-args' import { spawnSystemSshCommand } from './system-ssh-command' import { isWindowsRemoteHost, type RemoteHostPlatform } from './ssh-remote-platform' -import { powerShellCommand, powerShellLiteral } from './ssh-remote-powershell' +import { powerShellCommand } from './ssh-remote-powershell' +import { quotePowerShellLiteral as powerShellLiteral } from '../../shared/powershell-native-argument' import { awaitWithSystemSshAbort, throwIfAborted, @@ -23,12 +24,6 @@ import { type WindowsWriteSource } from './system-ssh-windows-write-strategy' -export { - WINDOWS_STDIN_WRITE_CHUNK_BYTES, - WINDOWS_STDIN_WRITE_TIMEOUT_MS -} from './system-ssh-windows-write-strategy' -export { WINDOWS_STAGED_WRITE_SUFFIX } from './system-ssh-windows-file-write' - type SystemSshOperationOptions = SystemSshBuildArgsOptions & { signal?: AbortSignal hostPlatform?: RemoteHostPlatform diff --git a/src/main/ssh/system-ssh-file-transfer.ts b/src/main/ssh/system-ssh-file-transfer.ts index 1abf3036f97..377435440fe 100644 --- a/src/main/ssh/system-ssh-file-transfer.ts +++ b/src/main/ssh/system-ssh-file-transfer.ts @@ -23,12 +23,11 @@ import { isHostAnsweredSystemSshExit, type ProcessResult } from './system-ssh-operation-lifecycle' +import { uploadFileViaSystemSsh, writeBufferViaSystemSsh } from './system-ssh-file-binary-transfer' import { - uploadFileViaSystemSsh, WINDOWS_STDIN_WRITE_CHUNK_BYTES, - WINDOWS_STDIN_WRITE_TIMEOUT_MS, - writeBufferViaSystemSsh -} from './system-ssh-file-binary-transfer' + WINDOWS_STDIN_WRITE_TIMEOUT_MS +} from './system-ssh-windows-write-strategy' import { isSftpPathUnsupportedError, isSftpUnavailableError, diff --git a/src/main/ssh/system-ssh-forward-process.ts b/src/main/ssh/system-ssh-forward-process.ts index e39e729deaa..a79a1086947 100644 --- a/src/main/ssh/system-ssh-forward-process.ts +++ b/src/main/ssh/system-ssh-forward-process.ts @@ -1,6 +1,7 @@ import { spawn, type ChildProcess } from 'node:child_process' import { connect, createServer } from 'node:net' -import { buildSshArgs, findSystemSsh, type SystemSshBuildArgsOptions } from './ssh-system-fallback' +import { buildSshArgs, type SystemSshBuildArgsOptions } from './system-ssh-args' +import { findSystemSsh } from './system-ssh-binary' import { stopOrphanedOrcaSshForward } from './system-ssh-orphaned-forward' import type { SshTarget } from '../../shared/ssh-types' diff --git a/src/main/ssh/ssh-system-fallback.test.ts b/src/main/ssh/system-ssh-operations.test.ts similarity index 99% rename from src/main/ssh/ssh-system-fallback.test.ts rename to src/main/ssh/system-ssh-operations.test.ts index 452b66ff9a8..5f3347509ff 100644 --- a/src/main/ssh/ssh-system-fallback.test.ts +++ b/src/main/ssh/system-ssh-operations.test.ts @@ -22,16 +22,14 @@ vi.mock('child_process', () => ({ spawn: spawnMock })) +import { buildSshArgs } from './system-ssh-args' import { - buildSshArgs, downloadFileViaSystemSsh, - spawnSystemSsh, - spawnSystemSshCommand, - uploadDirectoryViaSystemSsh, uploadFileViaSystemSsh, - writeBufferViaSystemSsh, - writeFileViaSystemSsh -} from './ssh-system-fallback' + writeBufferViaSystemSsh +} from './system-ssh-file-binary-transfer' +import { spawnSystemSsh, spawnSystemSshCommand } from './system-ssh-command' +import { uploadDirectoryViaSystemSsh, writeFileViaSystemSsh } from './system-ssh-file-transfer' import { getRemoteHostPlatform } from './ssh-remote-platform' import type { SshTarget } from '../../shared/ssh-types' import type { SystemSshResolvedConfig } from './ssh-control-socket' diff --git a/src/main/ssh/system-ssh-windows-file-write.ts b/src/main/ssh/system-ssh-windows-file-write.ts index 8b98d4aec50..5efc788aa77 100644 --- a/src/main/ssh/system-ssh-windows-file-write.ts +++ b/src/main/ssh/system-ssh-windows-file-write.ts @@ -1,5 +1,6 @@ import { randomBytes } from 'node:crypto' -import { powerShellCommand, powerShellLiteral } from './ssh-remote-powershell' +import { powerShellCommand } from './ssh-remote-powershell' +import { quotePowerShellLiteral as powerShellLiteral } from '../../shared/powershell-native-argument' import { normalizeWindowsRemotePath } from './ssh-remote-platform' /** diff --git a/src/main/ssh/system-ssh-windows-upload.test.ts b/src/main/ssh/system-ssh-windows-upload.test.ts index f28dc6075da..967923aefb7 100644 --- a/src/main/ssh/system-ssh-windows-upload.test.ts +++ b/src/main/ssh/system-ssh-windows-upload.test.ts @@ -43,13 +43,8 @@ vi.mock('./system-ssh-operation-lifecycle', async (importActual) => { }) import { uploadDirectoryViaSystemSsh } from './system-ssh-file-transfer' -import { - uploadFileViaSystemSsh, - WINDOWS_STAGED_WRITE_SUFFIX, - WINDOWS_STDIN_WRITE_CHUNK_BYTES, - WINDOWS_STDIN_WRITE_TIMEOUT_MS, - writeBufferViaSystemSsh -} from './system-ssh-file-binary-transfer' +import { uploadFileViaSystemSsh, writeBufferViaSystemSsh } from './system-ssh-file-binary-transfer' +import { WINDOWS_STAGED_WRITE_SUFFIX } from './system-ssh-windows-file-write' import { waitForChannelClose } from './system-ssh-operation-lifecycle' import * as windowsFileWrite from './system-ssh-windows-file-write' import { getRemoteHostPlatform } from './ssh-remote-platform' @@ -59,6 +54,8 @@ import { } from './system-ssh-windows-write-capabilities' import { explainWindowsPowerShellStdinFailure, + WINDOWS_STDIN_WRITE_CHUNK_BYTES, + WINDOWS_STDIN_WRITE_TIMEOUT_MS, writeWindowsRemoteFile } from './system-ssh-windows-write-strategy' import type { SshTarget } from '../../shared/ssh-types'