mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
111 lines
3.8 KiB
TypeScript
111 lines
3.8 KiB
TypeScript
import { glabExecFileAsync } from '../git/runner'
|
|
import { getSshGitProviderGeneration } from '../providers/ssh-git-dispatch'
|
|
import { DEFAULT_GITLAB_HOSTS, normalizeGitLabHost } from './project-ref-parser'
|
|
|
|
export type LocalGitExecOptions = {
|
|
wslDistro?: string
|
|
}
|
|
|
|
const GLAB_KNOWN_HOSTS_TIMEOUT_MS = 10_000
|
|
const knownHostsCacheByExecutionContext = new Map<string, readonly string[]>()
|
|
const knownHostsInFlightByExecutionContext = new Map<string, Promise<readonly string[]>>()
|
|
|
|
function knownHostsExecutionKey(
|
|
connectionId?: string | null,
|
|
localGitOptions: LocalGitExecOptions = {}
|
|
): string {
|
|
if (connectionId) {
|
|
// Why: reconnecting can replace the SSH/relay execution host under the same id.
|
|
return `connection:${connectionId}:${getSshGitProviderGeneration(connectionId)}`
|
|
}
|
|
return localGitOptions.wslDistro ? `wsl:${localGitOptions.wslDistro}` : 'native'
|
|
}
|
|
|
|
/** @internal - exposed for tests only */
|
|
export function _resetKnownHostsCache(): void {
|
|
knownHostsCacheByExecutionContext.clear()
|
|
knownHostsInFlightByExecutionContext.clear()
|
|
}
|
|
|
|
export function rememberGlabKnownHost(
|
|
host: string,
|
|
connectionId?: string | null,
|
|
localGitOptions: LocalGitExecOptions = {}
|
|
): void {
|
|
const normalizedHost = normalizeGitLabHost(host)
|
|
const key = knownHostsExecutionKey(connectionId, localGitOptions)
|
|
const cached = knownHostsCacheByExecutionContext.get(key)
|
|
if (!cached || cached.map(normalizeGitLabHost).includes(normalizedHost)) {
|
|
return
|
|
}
|
|
knownHostsCacheByExecutionContext.set(key, [...cached, normalizedHost])
|
|
}
|
|
|
|
export async function getGlabKnownHosts(
|
|
connectionId?: string | null,
|
|
localGitOptions: LocalGitExecOptions = {}
|
|
): Promise<readonly string[]> {
|
|
const key = knownHostsExecutionKey(connectionId, localGitOptions)
|
|
const cached = knownHostsCacheByExecutionContext.get(key)
|
|
if (cached) {
|
|
return cached
|
|
}
|
|
const inFlight = knownHostsInFlightByExecutionContext.get(key)
|
|
if (inFlight) {
|
|
return inFlight
|
|
}
|
|
const probe = probeGlabKnownHosts(key, connectionId, localGitOptions)
|
|
knownHostsInFlightByExecutionContext.set(key, probe)
|
|
try {
|
|
return await probe
|
|
} finally {
|
|
if (knownHostsInFlightByExecutionContext.get(key) === probe) {
|
|
knownHostsInFlightByExecutionContext.delete(key)
|
|
}
|
|
}
|
|
}
|
|
|
|
async function probeGlabKnownHosts(
|
|
key: string,
|
|
connectionId?: string | null,
|
|
localGitOptions: LocalGitExecOptions = {}
|
|
): Promise<readonly string[]> {
|
|
try {
|
|
// Why: auth config belongs to the executing host; do not share native, WSL,
|
|
// or reconnected SSH/relay results, and bound an otherwise global probe.
|
|
const { stdout, stderr } = await glabExecFileAsync(['auth', 'status'], {
|
|
timeout: GLAB_KNOWN_HOSTS_TIMEOUT_MS,
|
|
...(!connectionId && localGitOptions.wslDistro
|
|
? { wslDistro: localGitOptions.wslDistro }
|
|
: {})
|
|
})
|
|
const hosts = parseGlabAuthStatusHosts(`${stdout}\n${stderr}`)
|
|
const merged = Array.from(new Set([...DEFAULT_GITLAB_HOSTS, ...hosts]))
|
|
knownHostsCacheByExecutionContext.set(key, merged)
|
|
return merged
|
|
} catch {
|
|
// Keep failures uncached so auth or tunnel recovery is discovered later.
|
|
return [...DEFAULT_GITLAB_HOSTS]
|
|
}
|
|
}
|
|
|
|
export function parseGlabAuthStatusHosts(output: string): string[] {
|
|
const hosts = new Set<string>()
|
|
// Why: self-hosted GitLab can run on a non-default port; preserve it so
|
|
// services on the same hostname remain distinct downstream.
|
|
for (const match of output.matchAll(/logged in to ([a-zA-Z0-9.-]+(?::\d+)?)/gi)) {
|
|
hosts.add(match[1].toLowerCase())
|
|
}
|
|
for (const line of output.split('\n')) {
|
|
const bareLine = line.trim()
|
|
const hostLine = bareLine.endsWith(':') ? bareLine.slice(0, -1) : bareLine
|
|
if (
|
|
line === bareLine &&
|
|
/^[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?(?::\d+)?$/.test(hostLine)
|
|
) {
|
|
hosts.add(hostLine.toLowerCase())
|
|
}
|
|
}
|
|
return Array.from(hosts)
|
|
}
|