Merge pull request #4944 from Dokploy/feat/add-github-provider-baseurl

feat: add baseUrl to github provider to support github enterprise urls
This commit is contained in:
Mauricio Siu
2026-08-04 11:53:04 -06:00
committed by GitHub
18 changed files with 9627 additions and 14 deletions
+68 -2
View File
@@ -9,6 +9,68 @@ import { Octokit } from "octokit";
import { quote } from "shell-quote";
import type { z } from "zod";
export const DEFAULT_GITHUB_URL = "https://github.com";
export const DEFAULT_GITHUB_API_URL = "https://api.github.com";
export const parseGithubBaseUrl = (
githubUrl?: string | null,
): { url: string } | { error: string } => {
const raw = githubUrl?.trim();
if (!raw) {
return { url: DEFAULT_GITHUB_URL };
}
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw)
? raw
: `https://${raw}`;
let parsed: URL;
try {
parsed = new URL(withScheme);
} catch {
return { error: `"${raw}" is not a valid URL` };
}
if (parsed.protocol !== "https:") {
return { error: "Only https is supported for GitHub instances" };
}
// "acme.ghe.com." resolves the same as "acme.ghe.com", but the trailing dot
// would slip past the checks below.
if (parsed.hostname.endsWith(".")) {
parsed.hostname = parsed.hostname.replace(/\.+$/, "");
}
const { hostname } = parsed;
if (!hostname.includes(".")) {
return { error: `"${hostname}" is not a fully qualified hostname` };
}
return { url: `${parsed.protocol}//${parsed.host}` };
};
export const normalizeGithubUrl = (githubUrl?: string | null): string => {
const result = parseGithubBaseUrl(githubUrl);
return "url" in result ? result.url : DEFAULT_GITHUB_URL;
};
export const deriveGithubApiUrl = (githubUrl?: string | null): string => {
const normalized = normalizeGithubUrl(githubUrl);
const { protocol, host } = new URL(normalized);
if (host === "github.com" || host === "www.github.com") {
return DEFAULT_GITHUB_API_URL;
}
// Data residency tenants keep the api. prefix; GHES does not have one.
if (host === "ghe.com" || host.endsWith(".ghe.com")) {
return `${protocol}//api.${host}`;
}
return `${protocol}//${host}/api/v3`;
};
export const authGithub = (githubProvider: Github): Octokit => {
if (!haveGithubRequirements(githubProvider)) {
throw new TRPCError({
@@ -24,6 +86,7 @@ export const authGithub = (githubProvider: Github): Octokit => {
privateKey: githubProvider?.githubPrivateKey || "",
installationId: githubProvider?.githubInstallationId,
},
baseUrl: deriveGithubApiUrl(githubProvider?.githubUrl),
});
return octokit;
@@ -162,10 +225,11 @@ export const cloneGithubRepository = async ({
const outputPath = outputPathOverride ?? join(basePath, appName, "code");
const octokit = authGithub(githubProvider);
const token = await getGithubToken(octokit);
const repoclone = `github.com/${owner}/${repository}.git`;
const cloneBase = new URL(normalizeGithubUrl(githubProvider.githubUrl));
const repoclone = `${cloneBase.host}/${owner}/${repository}.git`;
command += `rm -rf ${outputPath};`;
command += `mkdir -p ${outputPath};`;
const cloneUrl = `https://oauth2:${token}@${repoclone}`;
const cloneUrl = `${cloneBase.protocol}//oauth2:${token}@${repoclone}`;
command += `echo ${quote([`Cloning Repo ${repoclone} to ${outputPath}: ✅`])};`;
command += `git clone --branch ${quote([String(branch ?? "")])} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${quote([String(cloneUrl ?? "")])} ${quote([String(outputPath ?? "")])} --progress;`;
@@ -187,6 +251,7 @@ export const getGithubRepositories = async (githubId?: string) => {
privateKey: githubProvider.githubPrivateKey,
installationId: githubProvider.githubInstallationId,
},
baseUrl: deriveGithubApiUrl(githubProvider.githubUrl),
});
const repositories = (await octokit.paginate(
@@ -213,6 +278,7 @@ export const getGithubBranches = async (
privateKey: githubProvider.githubPrivateKey,
installationId: githubProvider.githubInstallationId,
},
baseUrl: deriveGithubApiUrl(githubProvider.githubUrl),
});
const branches = (await octokit.paginate(octokit.rest.repos.listBranches, {