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
@@ -15,8 +15,11 @@ const {
normalizeBaseUrl,
GITLAB_AUTH_FILE,
DEFAULT_GITLAB_BASE_URL,
getGitLabDefaultBaseUrl,
} = await import('./auth.js');
const SETTINGS_FILE = path.join(TEMP_DATA_DIR, 'settings.json');
afterAll(() => {
fs.rmSync(TEMP_DATA_DIR, { recursive: true, force: true });
});
@@ -25,6 +28,9 @@ afterEach(() => {
if (fs.existsSync(GITLAB_AUTH_FILE)) {
fs.unlinkSync(GITLAB_AUTH_FILE);
}
if (fs.existsSync(SETTINGS_FILE)) {
fs.unlinkSync(SETTINGS_FILE);
}
});
const aliceUser = {
@@ -135,6 +141,19 @@ describe('multi-account switching', () => {
});
});
describe('getGitLabDefaultBaseUrl', () => {
test('falls back to the built-in default when nothing is configured', () => {
expect(getGitLabDefaultBaseUrl()).toBe(DEFAULT_GITLAB_BASE_URL);
});
test('returns the configured settings.json default when present', () => {
fs.writeFileSync(SETTINGS_FILE, JSON.stringify({
gitProviders: { gitlab: { apiBaseUrl: 'https://gitlab.example.com' } },
}));
expect(getGitLabDefaultBaseUrl()).toBe('https://gitlab.example.com');
});
});
describe('clearGitLabAuth', () => {
test('removes the current account and deletes the file when empty', () => {
setGitLabAuth({ accessToken: 'glpat-a', baseUrl: 'gitlab.com', user: aliceUser });