mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 16:34:36 +00:00
fix(projects): harden grouped project routing
This commit is contained in:
@@ -72,6 +72,22 @@ describe("deriveProjectGroupKey", () => {
|
||||
expect(derive("git@github.com:acme/foo.git")).toBe(derive("https://github.com/acme/foo"));
|
||||
});
|
||||
|
||||
test.each(["ssh://git@github.com/acme/foo.git", "ssh://git@ssh.github.com:443/acme/foo.git"])(
|
||||
"normalizes known forge SSH URLs across remote forms: %s",
|
||||
(remoteUrl) => {
|
||||
const rootPath = path.resolve("repo");
|
||||
const derive = (value: string) =>
|
||||
deriveProjectGroupKey({
|
||||
rootPath,
|
||||
remoteUrl: value,
|
||||
worktreeRoot: rootPath,
|
||||
mainRepoRoot: null,
|
||||
});
|
||||
|
||||
expect(derive(remoteUrl)).toBe(derive("git@github.com:acme/foo.git"));
|
||||
},
|
||||
);
|
||||
|
||||
test("accepts an SCP-style remote without a username", () => {
|
||||
const rootPath = path.resolve("repo");
|
||||
|
||||
|
||||
@@ -5,6 +5,13 @@ import { getRealpathAwareRelativePath } from "../utils/path.js";
|
||||
const CLOUD_FORGE_HOSTS = new Set(
|
||||
FORGE_DEFINITIONS.flatMap((forge) => forge.cloudHosts ?? []).map((host) => host.toLowerCase()),
|
||||
);
|
||||
const CANONICAL_CLOUD_FORGE_HOSTS = new Map(
|
||||
FORGE_DEFINITIONS.flatMap((forge) => {
|
||||
const hosts = forge.cloudHosts ?? [];
|
||||
const canonicalHost = hosts[0]?.toLowerCase();
|
||||
return canonicalHost ? hosts.map((host) => [host.toLowerCase(), canonicalHost] as const) : [];
|
||||
}),
|
||||
);
|
||||
const DEFAULT_REMOTE_PORTS: Partial<Record<string, string>> = {
|
||||
"git:": "9418",
|
||||
"git+ssh:": "22",
|
||||
@@ -82,8 +89,9 @@ function parseScpRemote(remoteUrl: string): RemoteLocation | null {
|
||||
|
||||
const preserveLeadingSlash = remotePath.startsWith("/");
|
||||
const user = match.groups?.user;
|
||||
const normalizedHost = normalizeCloudForgeHost(host);
|
||||
return {
|
||||
host,
|
||||
host: normalizedHost,
|
||||
path: remotePath,
|
||||
relativePathUser: user && user !== "git" && !preserveLeadingSlash ? user : null,
|
||||
preserveLeadingSlash,
|
||||
@@ -95,8 +103,10 @@ function parseScpRemote(remoteUrl: string): RemoteLocation | null {
|
||||
function parseUrlRemote(remoteUrl: string): RemoteLocation | null {
|
||||
try {
|
||||
const parsed = new URL(remoteUrl);
|
||||
const host = deriveRemoteHost(parsed);
|
||||
const preserveLeadingSlash = ["git+ssh:", "ssh:", "ssh+git:"].includes(parsed.protocol);
|
||||
const isSsh = ["git+ssh:", "ssh:", "ssh+git:"].includes(parsed.protocol);
|
||||
const forgeHost = deriveCanonicalUrlForgeHost(parsed, isSsh);
|
||||
const host = forgeHost ?? deriveRemoteHost(parsed);
|
||||
const preserveLeadingSlash = isSsh && !forgeHost;
|
||||
let remotePath = parsed.pathname || null;
|
||||
if (remotePath && !preserveLeadingSlash) remotePath = remotePath.replace(/^\/+/, "");
|
||||
if (!host || !remotePath) return null;
|
||||
@@ -106,15 +116,27 @@ function parseUrlRemote(remoteUrl: string): RemoteLocation | null {
|
||||
relativePathUser: null,
|
||||
preserveLeadingSlash,
|
||||
decodePercentEncoding: true,
|
||||
stripDotGitSuffix:
|
||||
(!parsed.port || parsed.port === DEFAULT_REMOTE_PORTS[parsed.protocol]) &&
|
||||
CLOUD_FORGE_HOSTS.has(parsed.hostname.toLowerCase()),
|
||||
stripDotGitSuffix: forgeHost !== null,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeCloudForgeHost(host: string): string {
|
||||
return CANONICAL_CLOUD_FORGE_HOSTS.get(host.toLowerCase()) ?? host;
|
||||
}
|
||||
|
||||
function deriveCanonicalUrlForgeHost(remoteUrl: URL, isSsh: boolean): string | null {
|
||||
const hostname = remoteUrl.hostname.toLowerCase();
|
||||
const canonicalHost = CANONICAL_CLOUD_FORGE_HOSTS.get(hostname);
|
||||
if (!canonicalHost) return null;
|
||||
const usesDefaultPort =
|
||||
!remoteUrl.port || remoteUrl.port === DEFAULT_REMOTE_PORTS[remoteUrl.protocol];
|
||||
const usesGithubSshAliasPort = isSsh && hostname === "ssh.github.com" && remoteUrl.port === "443";
|
||||
return usesDefaultPort || usesGithubSshAliasPort ? canonicalHost : null;
|
||||
}
|
||||
|
||||
function normalizeRemotePath(
|
||||
remotePath: string,
|
||||
preserveLeadingSlash: boolean,
|
||||
|
||||
Reference in New Issue
Block a user