mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-03 23:23:21 +00:00
* 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.
72 lines
2.4 KiB
TypeScript
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;
|
|
}
|