import type { AzureDevOpsOrganization, GitIntegrationConfig, GitIntegrationProvider, GitIntegrationSource, GitIntegrationSettings, } from "./types"; export const gitIntegrationProviders: GitIntegrationProvider[] = [ "github", "gitlab", "gitlab-self-hosted", "azure-devops", "gitea", ]; const defaults: Record> = { github: { enabled: false, baseUrl: "https://github.com", username: "", tokenStored: false, }, gitlab: { enabled: false, baseUrl: "https://gitlab.com", username: "", tokenStored: false, }, "gitlab-self-hosted": { enabled: false, baseUrl: "", username: "", tokenStored: false, }, "azure-devops": { enabled: false, baseUrl: "https://dev.azure.com/", username: "", tokenStored: false, }, gitea: { enabled: false, baseUrl: "", username: "", tokenStored: false, }, }; export function defaultGitIntegrationSettings(): GitIntegrationSettings { return { providers: Object.fromEntries( gitIntegrationProviders.map((provider) => [provider, { provider, ...defaults[provider] }]), ) as GitIntegrationSettings["providers"], azureDevOpsOrganizations: [], }; } function normaliseAzureOrganization(value: unknown): AzureDevOpsOrganization | null { if (!value || typeof value !== "object") return null; const stored = value as Partial; const id = typeof stored.id === "string" && /^[a-zA-Z0-9_-]{1,80}$/.test(stored.id) ? stored.id : ""; if (!id) return null; return { id, name: typeof stored.name === "string" ? stored.name : "", enabled: stored.enabled === true, baseUrl: typeof stored.baseUrl === "string" ? stored.baseUrl : "", username: typeof stored.username === "string" ? stored.username : "", tokenStored: stored.tokenStored === true, }; } export function normaliseGitIntegrationSettings(value: unknown): GitIntegrationSettings { const fallback = defaultGitIntegrationSettings(); if (!value || typeof value !== "object") return fallback; const storedProviders = (value as Partial).providers; if (!storedProviders || typeof storedProviders !== "object") return fallback; for (const provider of gitIntegrationProviders) { const stored = storedProviders[provider] as Partial | undefined; if (!stored || typeof stored !== "object") continue; fallback.providers[provider] = { provider, enabled: stored.enabled === true, baseUrl: typeof stored.baseUrl === "string" ? stored.baseUrl : fallback.providers[provider].baseUrl, username: typeof stored.username === "string" ? stored.username : "", tokenStored: stored.tokenStored === true, }; } const storedOrganizations = (value as Partial).azureDevOpsOrganizations; if (Array.isArray(storedOrganizations)) { const seen = new Set(); fallback.azureDevOpsOrganizations = storedOrganizations .map(normaliseAzureOrganization) .filter((organization): organization is AzureDevOpsOrganization => { if (!organization || seen.has(organization.id)) return false; seen.add(organization.id); return true; }); } else { const legacy = fallback.providers["azure-devops"]; const hasLegacyConfiguration = legacy.enabled || legacy.tokenStored || legacy.username.trim().length > 0 || !/^https:\/\/dev\.azure\.com\/?$/i.test(legacy.baseUrl.trim()); if (hasLegacyConfiguration) { fallback.azureDevOpsOrganizations = [{ id: "default", name: "Azure DevOps", enabled: legacy.enabled, baseUrl: legacy.baseUrl, username: legacy.username, tokenStored: legacy.tokenStored, }]; } } return fallback; } export function integrationCredentialKey(provider: GitIntegrationProvider, accountId?: string): string { if (provider === "azure-devops" && accountId && accountId !== "default") { return `integration:${provider}:${accountId}`; } return `integration:${provider}`; } export function configuredIntegrationSources(settings: GitIntegrationSettings): GitIntegrationSource[] { const sources: GitIntegrationSource[] = []; for (const provider of gitIntegrationProviders) { if (provider === "azure-devops") continue; const config = settings.providers[provider]; if (config.enabled && config.tokenStored && config.baseUrl.trim()) { sources.push({ id: provider, provider, label: providerLabel(provider), baseUrl: config.baseUrl }); } } for (const organization of settings.azureDevOpsOrganizations) { if (!organization.enabled || !organization.tokenStored || !organization.baseUrl.trim()) continue; sources.push({ id: `azure-devops:${organization.id}`, provider: "azure-devops", accountId: organization.id, label: organization.name.trim() || organizationNameFromUrl(organization.baseUrl) || "Azure DevOps", baseUrl: organization.baseUrl, }); } return sources; } export function configuredIntegrationCount(settings: GitIntegrationSettings): number { return configuredIntegrationSources(settings).length; } export function providerLabel(provider: GitIntegrationProvider): string { return { github: "GitHub", gitlab: "GitLab.com", "gitlab-self-hosted": "GitLab Self-Managed", "azure-devops": "Azure DevOps", gitea: "Gitea", }[provider]; } export function organizationNameFromUrl(value: string): string { try { const url = new URL(value); return url.pathname.split("/").filter(Boolean)[0] ?? ""; } catch { return ""; } }