Files
paseo/packages/server/src/utils/github-remote.ts
T
Mohamed Boudra 53c14d9855 Extract client SDK package (#1052)
* Extract client SDK package

* Polish SDK client identity defaults

* Build client before dependent CI jobs

* Restore daemon client server export

* Extract protocol and client SDK packages

* Fix provider override schema validation

* Fix app test daemon client imports

* Simplify workspace build targets

* Fix CLI test server build bootstrap

* Run SDK package tests in CI

* Fix rebase package split drift

* Restore lockfile registry metadata

* Update SDK config test for prompt default

* Move terminal stream router test to client package

* Fix rebase drift for protocol imports

* Fix SDK agent capability fixture

* Restore legacy server client exports

* Fix server export compatibility test

* Advertise custom mode icon client capability

* Remove server daemon-client exports

* Format rebased mode control import

* Fix rebase drift for protocol imports

Files added by upstream PRs (#893, #1147, #1154) referenced the pre-split
shared/ paths that this branch moves into @getpaseo/protocol. Redirect
those imports to the protocol package so typecheck stays green after the
rebase.
2026-05-28 01:58:18 +08:00

72 lines
2.4 KiB
TypeScript

import {
isGitHubHost,
normalizeHost,
parseGitHubRemoteIdentity,
parseGitRemoteLocation,
type GitHubRemoteIdentity as ResolvedGitHubRemoteIdentity,
} from "@getpaseo/protocol/git-remote";
import { findExecutable } from "./executable.js";
import { execCommand } from "./spawn.js";
let sshExecutableLookup: Promise<string | null> | null = null;
const sshHostnameResolutionCache = new Map<string, Promise<string | null>>();
export type SshHostnameResolver = (host: string) => Promise<string | null>;
export { parseGitHubRemoteUrl, type GitHubRemoteIdentity } from "@getpaseo/protocol/git-remote";
export async function resolveGitHubRemote(input: {
remoteUrl: string;
resolveSshHostname?: SshHostnameResolver;
}): Promise<ResolvedGitHubRemoteIdentity | null> {
const location = parseGitRemoteLocation(input.remoteUrl);
if (!location) return null;
if (isGitHubHost(location.host)) return parseGitHubRemoteIdentity(location.path);
if (location.transport !== "scp" && location.transport !== "ssh") return null;
const resolve = input.resolveSshHostname ?? resolveSshHostname;
const resolvedHost = await resolve(location.host);
if (!resolvedHost || !isGitHubHost(resolvedHost)) return null;
return parseGitHubRemoteIdentity(location.path);
}
export async function resolveSshHostname(host: string): Promise<string | null> {
const normalized = normalizeHost(host);
if (!normalized) return null;
const cached = sshHostnameResolutionCache.get(normalized);
if (cached) return cached;
const resolution = runSshHostnameLookup(normalized);
sshHostnameResolutionCache.set(normalized, resolution);
return resolution;
}
async function runSshHostnameLookup(host: string): Promise<string | null> {
sshExecutableLookup ??= findExecutable("ssh");
const sshPath = await sshExecutableLookup;
if (!sshPath) return null;
try {
const { stdout } = await execCommand(sshPath, ["-G", host], {
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
maxBuffer: 1024 * 1024,
});
return parseSshHostname(stdout);
} catch {
return null;
}
}
function parseSshHostname(stdout: string): string | null {
for (const line of stdout.split(/\r?\n/u)) {
const trimmed = line.trim();
if (!trimmed) continue;
const [key, value] = trimmed.split(/\s+/u);
if (key?.toLowerCase() !== "hostname") continue;
const normalized = normalizeHost(value ?? "");
if (normalized) return normalized;
}
return null;
}