mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 16:02:50 +00:00
Improve SSH remote Node.js prerequisite failures by adding package-manager-specific POSIX install guidance, Windows Node 18+ candidate validation, fnm XDG probing, and regression coverage while preserving SSH abort and session-limit behavior.
94 lines
3.3 KiB
TypeScript
94 lines
3.3 KiB
TypeScript
import type { SshConnection } from './ssh-connection'
|
|
import { createSshOperationAbortError } from './ssh-connection-utils'
|
|
import { execCommand } from './ssh-relay-deploy-helpers'
|
|
import { isSshSessionLimitError } from './ssh-session-limit-error'
|
|
|
|
export type RemoteNodeResolutionOptions = {
|
|
rethrowSessionLimitErrors?: boolean
|
|
signal?: AbortSignal
|
|
}
|
|
|
|
const NODE_PACKAGE_MANAGER_PROBE_TIMEOUT_MS = 8_000
|
|
|
|
const NODE_PACKAGE_MANAGER_HINTS: readonly { bin: string; label: string; install: string }[] = [
|
|
{ bin: 'apt-get', label: 'Debian/Ubuntu', install: 'sudo apt-get install -y nodejs npm' },
|
|
{ bin: 'dnf', label: 'Fedora/RHEL', install: 'sudo dnf install -y nodejs npm' },
|
|
{ bin: 'yum', label: 'RHEL/CentOS', install: 'sudo yum install -y nodejs npm' },
|
|
{ bin: 'pacman', label: 'Arch', install: 'sudo pacman -S --needed nodejs npm' },
|
|
{ bin: 'apk', label: 'Alpine', install: 'sudo apk add nodejs npm' },
|
|
{ bin: 'zypper', label: 'openSUSE', install: 'sudo zypper install -y nodejs npm' },
|
|
{ bin: 'brew', label: 'macOS/Homebrew', install: 'brew install node' }
|
|
]
|
|
|
|
export async function buildPosixNodeInstallGuidance(
|
|
conn: SshConnection,
|
|
options?: RemoteNodeResolutionOptions
|
|
): Promise<string> {
|
|
const detectedBin = await detectPackageManager(conn, options)
|
|
return formatNodeInstallHints(detectedBin)
|
|
}
|
|
|
|
async function detectPackageManager(
|
|
conn: SshConnection,
|
|
options?: RemoteNodeResolutionOptions
|
|
): Promise<string | null> {
|
|
throwIfAborted(options)
|
|
const bins = NODE_PACKAGE_MANAGER_HINTS.map((hint) => hint.bin).join(' ')
|
|
try {
|
|
const output = await execCommand(
|
|
conn,
|
|
`for p in ${bins}; do if command -v "$p" >/dev/null 2>&1; then echo "$p"; break; fi; done`,
|
|
commandOptions(options)
|
|
)
|
|
const detected = output.trim().split('\n')[0]
|
|
return NODE_PACKAGE_MANAGER_HINTS.some((hint) => hint.bin === detected) ? detected : null
|
|
} catch (err) {
|
|
if (options?.rethrowSessionLimitErrors && isSshSessionLimitError(err)) {
|
|
throw err
|
|
}
|
|
throwIfAborted(options)
|
|
return null
|
|
}
|
|
}
|
|
|
|
function formatNodeInstallHints(detectedBin: string | null): string {
|
|
const tailored = detectedBin
|
|
? NODE_PACKAGE_MANAGER_HINTS.find((hint) => hint.bin === detectedBin)
|
|
: null
|
|
const lines = [
|
|
'Node.js not found on remote host. Orca relay requires Node.js 18+ and npm.',
|
|
'',
|
|
'Install Node.js 18+ with npm on the remote host, then reconnect:'
|
|
]
|
|
if (tailored) {
|
|
lines.push(` ${tailored.label}: ${tailored.install}`)
|
|
} else {
|
|
for (const hint of NODE_PACKAGE_MANAGER_HINTS) {
|
|
lines.push(` ${hint.label}: ${hint.install}`)
|
|
}
|
|
}
|
|
lines.push(
|
|
'',
|
|
'Verify the remote runtime before reconnecting:',
|
|
' node --version # must be v18 or newer',
|
|
' npm --version',
|
|
'',
|
|
'If your distro package is older than Node 18, install an LTS release from https://nodejs.org/.'
|
|
)
|
|
return lines.join('\n')
|
|
}
|
|
|
|
function commandOptions(options?: RemoteNodeResolutionOptions): {
|
|
timeoutMs: number
|
|
signal?: AbortSignal
|
|
} {
|
|
const base = { timeoutMs: NODE_PACKAGE_MANAGER_PROBE_TIMEOUT_MS }
|
|
return options?.signal ? { ...base, signal: options.signal } : base
|
|
}
|
|
|
|
function throwIfAborted(options?: RemoteNodeResolutionOptions): void {
|
|
if (options?.signal?.aborted) {
|
|
throw createSshOperationAbortError()
|
|
}
|
|
}
|