feat(web,ui): per-provider git forge API base URL and detection URLs

Configure a default API base URL per git provider (github/gitlab/gitea)
in settings.json gitProviders, with GitHub Enterprise support (Octokit
baseUrl + device-flow web origin derived from the API base), and replace
the client-side custom-domains list with server-persisted detection URL
chips (SSH/HTTPS forms normalized to hosts). The configured API base host
auto-counts as a detection host. Settings round-trip through the existing
/api/config/settings sanitizer; the UI store hydrates from server settings
with a one-time localStorage migration.
This commit is contained in:
2026-08-17 09:55:11 +00:00
parent a87b3fd228
commit 0fc857959e
55 changed files with 1685 additions and 272 deletions
+13 -5
View File
@@ -321,7 +321,7 @@ export function registerGitHubRoutes(app) {
app.post('/api/github/auth/start', async (_req, res) => {
try {
const { getGitHubClientId, getGitHubScopes, startDeviceFlow } = await getGitHubLibraries();
const { getGitHubClientId, getGitHubScopes, startDeviceFlow, githubWebOriginFromApiBase, getProviderApiBaseUrl } = await getGitHubLibraries();
const clientId = getGitHubClientId();
if (!clientId) {
return res.status(400).json({
@@ -330,10 +330,12 @@ export function registerGitHubRoutes(app) {
}
const scope = getGitHubScopes();
const webOrigin = githubWebOriginFromApiBase(getProviderApiBaseUrl('github'));
const payload = await startDeviceFlow({
clientId,
scope,
webOrigin,
});
return res.json({
@@ -353,7 +355,7 @@ export function registerGitHubRoutes(app) {
app.post('/api/github/auth/complete', async (req, res) => {
try {
const { getGitHubClientId, exchangeDeviceCode, setGitHubAuth, getGitHubAuthAccounts } = await getGitHubLibraries();
const { getGitHubClientId, exchangeDeviceCode, setGitHubAuth, getGitHubAuthAccounts, githubWebOriginFromApiBase, getProviderApiBaseUrl } = await getGitHubLibraries();
const clientId = getGitHubClientId();
if (!clientId) {
return res.status(400).json({
@@ -369,7 +371,10 @@ export function registerGitHubRoutes(app) {
return res.status(400).json({ error: 'deviceCode is required' });
}
const payload = await exchangeDeviceCode({ clientId, deviceCode });
const apiBase = getProviderApiBaseUrl('github');
const webOrigin = githubWebOriginFromApiBase(apiBase);
const payload = await exchangeDeviceCode({ clientId, deviceCode, webOrigin });
if (payload?.error) {
return res.json({
@@ -385,7 +390,7 @@ export function registerGitHubRoutes(app) {
}
const { createOctokit } = await import('./octokit.js');
const octokit = createOctokit(accessToken);
const octokit = createOctokit(accessToken, apiBase);
const user = await getGitHubUserSummary(octokit);
setGitHubAuth({
@@ -1773,6 +1778,9 @@ export function registerGitHubRoutes(app) {
const { resolveGitHubRepoFromDirectory } = await import('./index.js');
const { resolveRepoNetwork } = await import('./repo/fork-detection.js');
const { getProviderApiBaseUrl } = await getGitHubLibraries();
const apiBase = getProviderApiBaseUrl('github');
const repoNetwork = await resolveRepoNetwork(octokit, directory);
const { repo } = await resolveGitHubRepoFromDirectory(directory);
if (!repo) {
@@ -1817,7 +1825,7 @@ export function registerGitHubRoutes(app) {
const issues = items
.filter((item) => !item?.pull_request)
.map((item) => {
const repoFullName = (item.repository_url || '').replace('https://api.github.com/repos/', '');
const repoFullName = (item.repository_url || '').replace(`${apiBase}/repos/`, '');
const matched = reposToQuery.find((r) => `${r.owner}/${r.repo}` === repoFullName);
return mapIssueSummary(item, matched || reposToQuery[0]);
});