feat(web,ui): built-in codeberg.org gitea host and provider detectUrls

This commit is contained in:
2026-08-17 09:56:59 +00:00
parent a30c7bce0a
commit 697925ee0d
22 changed files with 292 additions and 189 deletions
@@ -167,26 +167,30 @@ export const GitPage: React.FC<GitPageProps> = (props) => {
title={t('settings.page.git.title')}
showSaveStatus
>
<div className="flex h-8 min-w-0">
<SortableTabsStrip
items={providerTabs}
activeId={activeProviderTab}
onSelect={(tabId) => setActiveProviderTab(tabId as GitProviderTabId)}
layoutMode="fit"
variant="active-pill"
activePillButtonClassName="h-7"
/>
</div>
<section className="overflow-hidden rounded-lg border border-[var(--interactive-border)] bg-[var(--surface-muted)]">
<div className="border-b border-[var(--surface-subtle)] px-3 py-2">
<div className="flex h-8 min-w-0">
<SortableTabsStrip
items={providerTabs}
activeId={activeProviderTab}
onSelect={(tabId) => setActiveProviderTab(tabId as GitProviderTabId)}
layoutMode="fit"
variant="active-pill"
activePillButtonClassName="h-7"
/>
</div>
</div>
<div role="tabpanel" aria-label={t('settings.git.tabs.github')} hidden={activeProviderTab !== 'github'}>
<GitHubSettings />
</div>
<div role="tabpanel" aria-label={t('settings.git.tabs.gitlab')} hidden={activeProviderTab !== 'gitlab'}>
<GitLabSettings />
</div>
<div role="tabpanel" aria-label={t('settings.git.tabs.gitea')} hidden={activeProviderTab !== 'gitea'}>
<GiteaSettings />
</div>
<div role="tabpanel" aria-label={t('settings.git.tabs.github')} hidden={activeProviderTab !== 'github'}>
<GitHubSettings embedded />
</div>
<div role="tabpanel" aria-label={t('settings.git.tabs.gitlab')} hidden={activeProviderTab !== 'gitlab'}>
<GitLabSettings embedded />
</div>
<div role="tabpanel" aria-label={t('settings.git.tabs.gitea')} hidden={activeProviderTab !== 'gitea'}>
<GiteaSettings embedded />
</div>
</section>
<SettingsSection
title={t('settings.gitIdentities.page.section.title')}
@@ -36,7 +36,7 @@ type DeviceFlowCompleteResponse =
| { connected: true; user: GitHubUser; scope?: string }
| { connected: false; status?: string; error?: string };
export const GitHubSettings: React.FC = () => {
export const GitHubSettings: React.FC<{ embedded?: boolean }> = ({ embedded = false }) => {
const { t } = useI18n();
const { isMobile } = useDeviceInfo();
const runtimeGitHub = getRegisteredRuntimeAPIs()?.github;
@@ -271,14 +271,8 @@ export const GitHubSettings: React.FC = () => {
? t('settings.github.page.accountSource.cli')
: t('settings.github.page.accountSource.oauth');
return (
const accountContent = (
<>
<SettingsSection
title={t('settings.github.page.oauth.title')}
divider={false}
settingsItem="git.github-account"
info={t('settings.github.page.tooltip.connectAccount')}
>
<div className="rounded-lg bg-[var(--surface-elevated)]/70 overflow-hidden flex flex-col">
{connected ? (
<div className={cn("px-4 py-3", isMobile ? "flex flex-col gap-3" : "flex items-center justify-between gap-4")}>
@@ -447,65 +441,95 @@ export const GitHubSettings: React.FC = () => {
</div>
)}
<div className="flex flex-col gap-4 pt-4">
<div className={cn('flex flex-col gap-4', embedded ? 'border-t border-[var(--surface-subtle)] pt-4' : 'pt-4')}>
<ProviderApiBaseUrlInput provider="github" />
<ProviderDetectUrlsInput provider="github" />
</div>
</>
);
</SettingsSection>
{ghCli?.available && !ghCli?.active && (!ghCli.user || ghCli.disabled) && (
<SettingsSection title={t('settings.github.page.ghCli.title')}>
<div className="rounded-lg bg-[var(--surface-elevated)]/70 overflow-hidden">
<div className={cn("px-4 py-3", isMobile ? "flex flex-col gap-3" : "flex items-center justify-between gap-4")}>
<div className={cn("flex min-w-0 items-center gap-4", isMobile ? "w-full" : undefined)}>
{ghCli.user?.avatarUrl ? (
<img
src={ghCli.user.avatarUrl}
alt={ghCli.user.login ? t('settings.github.page.avatarAlt.withLogin', { login: ghCli.user.login }) : t('settings.github.page.avatarAlt.fallback')}
className="h-10 w-10 shrink-0 rounded-full border border-[var(--interactive-border)] bg-[var(--surface-muted)] object-cover"
loading="lazy"
referrerPolicy="no-referrer"
/>
) : (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-[var(--interactive-border)] bg-[var(--surface-muted)]">
<Icon name="github-fill" className="h-4 w-4 text-muted-foreground" />
</div>
)}
<div className="min-w-0 flex-1">
{!ghCli.disabled && ghCli.user && (
<div className="typography-ui-label text-foreground truncate">
{ghCli.user.name?.trim() || ghCli.user.login || 'GitHub'}
</div>
)}
{!ghCli.disabled && ghCli.user?.login && (
<div className={cn("flex items-center gap-2 typography-meta text-muted-foreground mt-0.5", isMobile ? "flex-wrap" : "truncate")}>
<Icon name="github-fill" className="h-3.5 w-3.5 shrink-0" />
<span className="font-mono">{ghCli.user.login}</span>
{ghCli.user.email && <span className="opacity-50"></span>}
{ghCli.user.email && <span>{ghCli.user.email}</span>}
</div>
)}
<div className={cn("typography-meta text-muted-foreground", ghCli.disabled ? "opacity-60" : undefined)}>
{ghCli.disabled
? t('settings.github.page.ghCli.disabledDescription')
: t('settings.github.page.ghCli.fallbackDescription')}
</div>
</div>
const ghCliContent = ghCli?.available && !ghCli?.active && (!ghCli.user || ghCli.disabled) ? (
<div className="rounded-lg bg-[var(--surface-elevated)]/70 overflow-hidden">
<div className={cn("px-4 py-3", isMobile ? "flex flex-col gap-3" : "flex items-center justify-between gap-4")}>
<div className={cn("flex min-w-0 items-center gap-4", isMobile ? "w-full" : undefined)}>
{ghCli.user?.avatarUrl ? (
<img
src={ghCli.user.avatarUrl}
alt={ghCli.user.login ? t('settings.github.page.avatarAlt.withLogin', { login: ghCli.user.login }) : t('settings.github.page.avatarAlt.fallback')}
className="h-10 w-10 shrink-0 rounded-full border border-[var(--interactive-border)] bg-[var(--surface-muted)] object-cover"
loading="lazy"
referrerPolicy="no-referrer"
/>
) : (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-[var(--interactive-border)] bg-[var(--surface-muted)]">
<Icon name="github-fill" className="h-4 w-4 text-muted-foreground" />
</div>
)}
<div className="min-w-0 flex-1">
{!ghCli.disabled && ghCli.user && (
<div className="typography-ui-label text-foreground truncate">
{ghCli.user.name?.trim() || ghCli.user.login || 'GitHub'}
</div>
<Button
size="sm"
variant="outline"
onClick={() => toggleGhCli(!ghCli.disabled)}
disabled={isBusy}
className={cn(isMobile ? "w-full" : undefined)}
>
{ghCli.disabled
? t('settings.github.page.ghCli.actions.enable')
: t('settings.github.page.ghCli.actions.disable')}
</Button>
)}
{!ghCli.disabled && ghCli.user?.login && (
<div className={cn("flex items-center gap-2 typography-meta text-muted-foreground mt-0.5", isMobile ? "flex-wrap" : "truncate")}>
<Icon name="github-fill" className="h-3.5 w-3.5 shrink-0" />
<span className="font-mono">{ghCli.user.login}</span>
{ghCli.user.email && <span className="opacity-50"></span>}
{ghCli.user.email && <span>{ghCli.user.email}</span>}
</div>
)}
<div className={cn("typography-meta text-muted-foreground", ghCli.disabled ? "opacity-60" : undefined)}>
{ghCli.disabled
? t('settings.github.page.ghCli.disabledDescription')
: t('settings.github.page.ghCli.fallbackDescription')}
</div>
</div>
</div>
<Button
size="sm"
variant="outline"
onClick={() => toggleGhCli(!ghCli.disabled)}
disabled={isBusy}
className={cn(isMobile ? "w-full" : undefined)}
>
{ghCli.disabled
? t('settings.github.page.ghCli.actions.enable')
: t('settings.github.page.ghCli.actions.disable')}
</Button>
</div>
</div>
) : null;
if (embedded) {
return (
<>
<div data-settings-item="git.github-account" className="p-4">
{accountContent}
</div>
{ghCliContent && (
<div className="border-t border-[var(--surface-subtle)] px-4 py-4">
{ghCliContent}
</div>
)}
</>
);
}
return (
<>
<SettingsSection
title={t('settings.github.page.oauth.title')}
divider={false}
settingsItem="git.github-account"
info={t('settings.github.page.tooltip.connectAccount')}
>
{accountContent}
</SettingsSection>
{ghCliContent && (
<SettingsSection title={t('settings.github.page.ghCli.title')}>
{ghCliContent}
</SettingsSection>
)}
</>
@@ -24,7 +24,7 @@ const getBaseUrlHost = (baseUrl?: string | null): string => {
}
};
export const GitLabSettings: React.FC = () => {
export const GitLabSettings: React.FC<{ embedded?: boolean }> = ({ embedded = false }) => {
const { t } = useI18n();
const { isMobile } = useDeviceInfo();
const runtimeGitLab = getRegisteredRuntimeAPIs()?.gitlab;
@@ -154,13 +154,8 @@ export const GitLabSettings: React.FC = () => {
const currentAccount = accounts.find((account) => account.current) ?? (accounts.length > 0 ? accounts[0] : null);
const currentBaseUrlHost = getBaseUrlHost(currentAccount?.baseUrl ?? status?.defaultBaseUrl);
return (
<SettingsSection
title={t('settings.gitlab.page.title')}
description={t('settings.gitlab.page.description')}
info={t('settings.gitlab.page.tooltip.connectAccount')}
settingsItem="git.gitlab-account"
>
const sectionContent = (
<>
<div className="rounded-lg bg-[var(--surface-elevated)]/70 overflow-hidden flex flex-col">
{connected ? (
<div className={cn('px-4 py-3', isMobile ? 'flex flex-col gap-3' : 'flex items-center justify-between gap-4')}>
@@ -296,10 +291,29 @@ export const GitLabSettings: React.FC = () => {
)}
</div>
<div className="flex flex-col gap-4 pt-4">
<div className={cn('flex flex-col gap-4', embedded ? 'border-t border-[var(--surface-subtle)] pt-4' : 'pt-4')}>
<ProviderApiBaseUrlInput provider="gitlab" />
<ProviderDetectUrlsInput provider="gitlab" />
</div>
</>
);
if (embedded) {
return (
<div data-settings-item="git.gitlab-account" className="p-4">
{sectionContent}
</div>
);
}
return (
<SettingsSection
title={t('settings.gitlab.page.title')}
description={t('settings.gitlab.page.description')}
info={t('settings.gitlab.page.tooltip.connectAccount')}
settingsItem="git.gitlab-account"
>
{sectionContent}
</SettingsSection>
);
};
@@ -24,7 +24,7 @@ const getBaseUrlHost = (baseUrl?: string | null): string => {
}
};
export const GiteaSettings: React.FC = () => {
export const GiteaSettings: React.FC<{ embedded?: boolean }> = ({ embedded = false }) => {
const { t } = useI18n();
const { isMobile } = useDeviceInfo();
const runtimeGitea = getRegisteredRuntimeAPIs()?.gitea;
@@ -159,13 +159,8 @@ export const GiteaSettings: React.FC = () => {
const currentAccount = accounts.find((account) => account.current) ?? (accounts.length > 0 ? accounts[0] : null);
const currentBaseUrlHost = getBaseUrlHost(currentAccount?.baseUrl);
return (
<SettingsSection
title={t('settings.gitea.page.title')}
description={t('settings.gitea.page.description')}
info={t('settings.gitea.page.tooltip.connectAccount')}
settingsItem="git.gitea-account"
>
const sectionContent = (
<>
<div className="rounded-lg bg-[var(--surface-elevated)]/70 overflow-hidden flex flex-col">
{connected ? (
<div className={cn('px-4 py-3', isMobile ? 'flex flex-col gap-3' : 'flex items-center justify-between gap-4')}>
@@ -306,10 +301,29 @@ export const GiteaSettings: React.FC = () => {
)}
</div>
<div className="flex flex-col gap-4 pt-4">
<div className={cn('flex flex-col gap-4', embedded ? 'border-t border-[var(--surface-subtle)] pt-4' : 'pt-4')}>
<ProviderApiBaseUrlInput provider="gitea" />
<ProviderDetectUrlsInput provider="gitea" />
</div>
</>
);
if (embedded) {
return (
<div data-settings-item="git.gitea-account" className="p-4">
{sectionContent}
</div>
);
}
return (
<SettingsSection
title={t('settings.gitea.page.title')}
description={t('settings.gitea.page.description')}
info={t('settings.gitea.page.tooltip.connectAccount')}
settingsItem="git.gitea-account"
>
{sectionContent}
</SettingsSection>
);
};
+3 -2
View File
@@ -88,8 +88,9 @@ describe('detectGitProvider', () => {
expect(detectGitProvider(['ssh://git@[2001:db8::1]/owner/repo.git'], hosts)).toBe('gitea');
});
test('does not classify codeberg.org as gitea without a configured host', () => {
expect(detectGitProvider(['git@codeberg.org:owner/repo.git'], EMPTY_HOSTS)).toBe('other');
test('classifies codeberg.org as gitea by default (built-in host)', () => {
expect(detectGitProvider(['git@codeberg.org:owner/repo.git'], EMPTY_HOSTS)).toBe('gitea');
expect(detectGitProvider(['https://codeberg.org/owner/repo.git'], EMPTY_HOSTS)).toBe('gitea');
});
test('github wins over a configured gitea host when both remotes are present', () => {
+8 -7
View File
@@ -15,8 +15,9 @@ export type GitProvider = 'github' | 'gitlab' | 'gitea' | 'other';
/**
* Per-provider hostname sets used for detection: custom user-configured
* domains (from the domains store), account-derived base-URL hostnames, and the
* configured api base host. Built-in defaults (github.com, gitlab.com) are
* applied inside the detection logic and never need to be present here.
* configured api base host. Built-in defaults (github.com, gitlab.com,
* codeberg.org) are applied inside the detection logic and never need to be
* present here.
*/
export type GitProviderHosts = {
github: string[];
@@ -91,10 +92,10 @@ export const buildGitProviderHosts = (input: {
/**
* Classify a repository by the hosts of its remotes. Returns null when there
* are no remotes to inspect. Built-in defaults apply always: `github.com` is
* GitHub and `gitlab.com` is GitLab (there is no built-in Gitea host). Custom
* hosts from `hosts.{github,gitlab,gitea}` are then matched in precedence
* order github -> gitlab -> gitea (first match wins). Anything else resolves
* to 'other' so GitHub-branded UI is never offered for a non-GitHub repo.
* GitHub, `gitlab.com` is GitLab, and `codeberg.org` is Gitea. Custom hosts
* from `hosts.{github,gitlab,gitea}` are then matched in precedence order
* github -> gitlab -> gitea (first match wins). Anything else resolves to
* 'other' so GitHub-branded UI is never offered for a non-GitHub repo.
*/
export const detectGitProvider = (fetchUrls: string[], hosts: GitProviderHosts): GitProvider | null => {
const remoteHosts = new Set<string>();
@@ -110,7 +111,7 @@ export const detectGitProvider = (fetchUrls: string[], hosts: GitProviderHosts):
const githubHosts = new Set(['github.com', ...normalizeHostList(hosts.github)]);
const gitlabHosts = new Set(['gitlab.com', ...normalizeHostList(hosts.gitlab)]);
const giteaHosts = new Set(normalizeHostList(hosts.gitea));
const giteaHosts = new Set(['codeberg.org', ...normalizeHostList(hosts.gitea)]);
for (const host of remoteHosts) {
if (githubHosts.has(host)) {
@@ -1639,10 +1639,10 @@ export const settingsDict = {
'settings.github.page.toast.ghCliDisabled': 'gh CLI Fallback deaktiviert',
'settings.github.page.toast.ghCliUpdateFailed': 'Fehler beim Aktualisieren der gh CLI Einstellung',
'settings.github.page.apiBaseUrl.label': 'API-Basis-URL',
'settings.github.page.apiBaseUrl.placeholder': 'https://github.example.com/api/v3',
'settings.github.page.apiBaseUrl.placeholder': 'https://api.github.com',
'settings.github.page.apiBaseUrl.description': 'Standard-Basis-URL für API-Aufrufe. Für GitHub Enterprise verwende die Adresse deines Servers, z. B. https://github.example.com/api/v3.',
'settings.github.page.detectUrls.label': 'Erkennungs-URLs',
'settings.github.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.github.page.detectUrls.placeholder': 'github.com',
'settings.github.page.detectUrls.description': 'Repositories, die per SSH oder HTTPS von diesen Hosts geklont wurden, werden als GitHub erkannt.',
'settings.gitlab.page.title': 'GitLab Personal Access Token',
'settings.gitlab.page.description': 'Fügen Sie ein GitLab Personal Access Token ein, um eine Verbindung herzustellen. Legen Sie die Basis-URL fest, wenn Sie eine selbst gehostete GitLab-Instanz verwenden.',
@@ -1652,10 +1652,10 @@ export const settingsDict = {
'settings.gitlab.page.baseUrl.label': 'Basis-URL (optional)',
'settings.gitlab.page.baseUrl.placeholder': 'https://gitlab.com',
'settings.gitlab.page.apiBaseUrl.label': 'API-Basis-URL',
'settings.gitlab.page.apiBaseUrl.placeholder': 'https://gitlab.example.com',
'settings.gitlab.page.apiBaseUrl.placeholder': 'https://gitlab.com',
'settings.gitlab.page.apiBaseUrl.description': 'Standard-Basis-URL für API-Aufrufe. Für selbst gehostete Instanzen verwende die Adresse deines Servers, z. B. https://gitlab.example.com.',
'settings.gitlab.page.detectUrls.label': 'Erkennungs-URLs',
'settings.gitlab.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.gitlab.page.detectUrls.placeholder': 'gitlab.com',
'settings.gitlab.page.detectUrls.description': 'Repositories, die per SSH oder HTTPS von diesen Hosts geklont wurden, werden als GitLab erkannt.',
'settings.gitlab.page.actions.connect': 'GitLab verbinden',
'settings.gitlab.page.actions.disconnect': 'Trennen',
@@ -1697,10 +1697,10 @@ export const settingsDict = {
'settings.gitea.page.toast.accountSwitched': 'Gitea-Konto gewechselt',
'settings.gitea.page.toast.accountSwitchFailed': 'Wechsel des Gitea-Kontos fehlgeschlagen',
'settings.gitea.page.apiBaseUrl.label': 'API-Basis-URL',
'settings.gitea.page.apiBaseUrl.placeholder': 'https://gitea.example.com',
'settings.gitea.page.apiBaseUrl.placeholder': 'https://codeberg.org',
'settings.gitea.page.apiBaseUrl.description': 'Standard-Basis-URL für API-Aufrufe. Für selbst gehostete Instanzen verwende die Adresse deines Servers, z. B. https://gitea.example.com.',
'settings.gitea.page.detectUrls.label': 'Erkennungs-URLs',
'settings.gitea.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.gitea.page.detectUrls.placeholder': 'codeberg.org',
'settings.gitea.page.detectUrls.description': 'Repositories, die per SSH oder HTTPS von diesen Hosts geklont wurden, werden als Gitea oder Forgejo erkannt.',
'settings.gitProviders.detectUrls.add': 'Erkennungs-URL hinzufügen',
'settings.gitProviders.detectUrls.remove': '{host} entfernen',
@@ -1705,10 +1705,10 @@ export const settingsDict = {
'settings.github.page.toast.ghCliDisabled': 'gh CLI fallback disabled',
'settings.github.page.toast.ghCliUpdateFailed': 'Failed to update gh CLI setting',
'settings.github.page.apiBaseUrl.label': 'API base URL',
'settings.github.page.apiBaseUrl.placeholder': 'https://github.example.com/api/v3',
'settings.github.page.apiBaseUrl.placeholder': 'https://api.github.com',
'settings.github.page.apiBaseUrl.description': 'Default base URL for API calls. For GitHub Enterprise, use your server address, e.g. https://github.example.com/api/v3.',
'settings.github.page.detectUrls.label': 'Detection URLs',
'settings.github.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.github.page.detectUrls.placeholder': 'github.com',
'settings.github.page.detectUrls.description': 'Repos cloned over SSH or HTTPS from these hosts are recognized as GitHub.',
'settings.gitlab.page.title': 'GitLab Personal Access Token',
'settings.gitlab.page.description': 'Paste a GitLab personal access token to connect. Set the base URL when using a self-hosted GitLab instance.',
@@ -1718,10 +1718,10 @@ export const settingsDict = {
'settings.gitlab.page.baseUrl.label': 'Base URL (optional)',
'settings.gitlab.page.baseUrl.placeholder': 'https://gitlab.com',
'settings.gitlab.page.apiBaseUrl.label': 'API base URL',
'settings.gitlab.page.apiBaseUrl.placeholder': 'https://gitlab.example.com',
'settings.gitlab.page.apiBaseUrl.placeholder': 'https://gitlab.com',
'settings.gitlab.page.apiBaseUrl.description': 'Default base URL for API calls. For self-hosted instances, use your server address, e.g. https://gitlab.example.com.',
'settings.gitlab.page.detectUrls.label': 'Detection URLs',
'settings.gitlab.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.gitlab.page.detectUrls.placeholder': 'gitlab.com',
'settings.gitlab.page.detectUrls.description': 'Repos cloned over SSH or HTTPS from these hosts are recognized as GitLab.',
'settings.gitlab.page.actions.connect': 'Connect GitLab',
'settings.gitlab.page.actions.disconnect': 'Disconnect',
@@ -1763,10 +1763,10 @@ export const settingsDict = {
'settings.gitea.page.toast.accountSwitched': 'Gitea account switched',
'settings.gitea.page.toast.accountSwitchFailed': 'Failed to switch Gitea account',
'settings.gitea.page.apiBaseUrl.label': 'API base URL',
'settings.gitea.page.apiBaseUrl.placeholder': 'https://gitea.example.com',
'settings.gitea.page.apiBaseUrl.placeholder': 'https://codeberg.org',
'settings.gitea.page.apiBaseUrl.description': 'Default base URL for API calls. For self-hosted instances, use your server address, e.g. https://gitea.example.com.',
'settings.gitea.page.detectUrls.label': 'Detection URLs',
'settings.gitea.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.gitea.page.detectUrls.placeholder': 'codeberg.org',
'settings.gitea.page.detectUrls.description': 'Repos cloned over SSH or HTTPS from these hosts are recognized as Gitea or Forgejo.',
'settings.gitProviders.detectUrls.add': 'Add a detection URL',
'settings.gitProviders.detectUrls.remove': 'Remove {host}',
@@ -1682,10 +1682,10 @@ export const settingsDict = {
"settings.github.page.toast.ghCliDisabled": "Respaldo de gh CLI desactivado",
"settings.github.page.toast.ghCliUpdateFailed": "No se pudo actualizar la configuración de gh CLI",
"settings.github.page.apiBaseUrl.label": "URL base de la API",
"settings.github.page.apiBaseUrl.placeholder": "https://github.example.com/api/v3",
"settings.github.page.apiBaseUrl.placeholder": "https://api.github.com",
"settings.github.page.apiBaseUrl.description": "URL base por defecto para las llamadas a la API. Para GitHub Enterprise, usa la dirección de tu servidor, p. ej. https://github.example.com/api/v3.",
"settings.github.page.detectUrls.label": "URL de detección",
"settings.github.page.detectUrls.placeholder": "ssh://git@git.example.com:2222, https://git.example.com",
"settings.github.page.detectUrls.placeholder": "github.com",
"settings.github.page.detectUrls.description": "Los repositorios clonados por SSH o HTTPS desde estos hosts se reconocen como GitHub.",
"settings.gitlab.page.title": "Token de acceso personal de GitLab",
"settings.gitlab.page.description": "Pega un token de acceso personal de GitLab para conectarte. Establece la URL base cuando uses una instancia de GitLab autoalojada.",
@@ -1695,10 +1695,10 @@ export const settingsDict = {
"settings.gitlab.page.baseUrl.label": "URL base (opcional)",
"settings.gitlab.page.baseUrl.placeholder": "https://gitlab.com",
"settings.gitlab.page.apiBaseUrl.label": "URL base de la API",
"settings.gitlab.page.apiBaseUrl.placeholder": "https://gitlab.example.com",
"settings.gitlab.page.apiBaseUrl.placeholder": "https://gitlab.com",
"settings.gitlab.page.apiBaseUrl.description": "URL base por defecto para las llamadas a la API. Para instancias autoalojadas, usa la dirección de tu servidor, p. ej. https://gitlab.example.com.",
"settings.gitlab.page.detectUrls.label": "URL de detección",
"settings.gitlab.page.detectUrls.placeholder": "ssh://git@git.example.com:2222, https://git.example.com",
"settings.gitlab.page.detectUrls.placeholder": "gitlab.com",
"settings.gitlab.page.detectUrls.description": "Los repositorios clonados por SSH o HTTPS desde estos hosts se reconocen como GitLab.",
"settings.gitlab.page.actions.connect": "Conectar GitLab",
"settings.gitlab.page.actions.disconnect": "Desconectar",
@@ -1740,10 +1740,10 @@ export const settingsDict = {
"settings.gitea.page.toast.accountSwitched": "Cuenta de Gitea cambiada",
"settings.gitea.page.toast.accountSwitchFailed": "No se pudo cambiar la cuenta de Gitea",
"settings.gitea.page.apiBaseUrl.label": "URL base de la API",
"settings.gitea.page.apiBaseUrl.placeholder": "https://gitea.example.com",
"settings.gitea.page.apiBaseUrl.placeholder": "https://codeberg.org",
"settings.gitea.page.apiBaseUrl.description": "URL base por defecto para las llamadas a la API. Para instancias autoalojadas, usa la dirección de tu servidor, p. ej. https://gitea.example.com.",
"settings.gitea.page.detectUrls.label": "URL de detección",
"settings.gitea.page.detectUrls.placeholder": "ssh://git@git.example.com:2222, https://git.example.com",
"settings.gitea.page.detectUrls.placeholder": "codeberg.org",
"settings.gitea.page.detectUrls.description": "Los repositorios clonados por SSH o HTTPS desde estos hosts se reconocen como Gitea o Forgejo.",
"settings.gitProviders.detectUrls.add": "Añadir una URL de detección",
"settings.gitProviders.detectUrls.remove": "Quitar {host}",
@@ -1600,10 +1600,10 @@ export const settingsDict = {
'settings.github.page.toast.ghCliDisabled': 'Solution de secours gh CLI désactivée',
'settings.github.page.toast.ghCliUpdateFailed': 'Échec de la mise à jour du paramètre gh CLI',
'settings.github.page.apiBaseUrl.label': 'URL de base de lAPI',
'settings.github.page.apiBaseUrl.placeholder': 'https://github.example.com/api/v3',
'settings.github.page.apiBaseUrl.placeholder': 'https://api.github.com',
'settings.github.page.apiBaseUrl.description': 'URL de base par défaut pour les appels API. Pour GitHub Enterprise, utilisez ladresse de votre serveur, ex. https://github.example.com/api/v3.',
'settings.github.page.detectUrls.label': 'URL de détection',
'settings.github.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.github.page.detectUrls.placeholder': 'github.com',
'settings.github.page.detectUrls.description': 'Les dépôts clonés en SSH ou HTTPS depuis ces hôtes sont reconnus comme GitHub.',
'settings.gitlab.page.title': 'Jeton d\'accès personnel GitLab',
'settings.gitlab.page.description': 'Collez un jeton d\'accès personnel GitLab pour vous connecter. Définissez l\'URL de base si vous utilisez une instance GitLab auto-hébergée.',
@@ -1613,10 +1613,10 @@ export const settingsDict = {
'settings.gitlab.page.baseUrl.label': 'URL de base (facultatif)',
'settings.gitlab.page.baseUrl.placeholder': 'https://gitlab.com',
'settings.gitlab.page.apiBaseUrl.label': 'URL de base de lAPI',
'settings.gitlab.page.apiBaseUrl.placeholder': 'https://gitlab.example.com',
'settings.gitlab.page.apiBaseUrl.placeholder': 'https://gitlab.com',
'settings.gitlab.page.apiBaseUrl.description': 'URL de base par défaut pour les appels API. Pour les instances auto-hébergées, utilisez ladresse de votre serveur, ex. https://gitlab.example.com.',
'settings.gitlab.page.detectUrls.label': 'URL de détection',
'settings.gitlab.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.gitlab.page.detectUrls.placeholder': 'gitlab.com',
'settings.gitlab.page.detectUrls.description': 'Les dépôts clonés en SSH ou HTTPS depuis ces hôtes sont reconnus comme GitLab.',
'settings.gitlab.page.actions.connect': 'Connecter GitLab',
'settings.gitlab.page.actions.disconnect': 'Déconnecter',
@@ -1658,10 +1658,10 @@ export const settingsDict = {
'settings.gitea.page.toast.accountSwitched': 'Le compte Gitea a changé',
'settings.gitea.page.toast.accountSwitchFailed': 'Échec du changement de compte Gitea',
'settings.gitea.page.apiBaseUrl.label': 'URL de base de lAPI',
'settings.gitea.page.apiBaseUrl.placeholder': 'https://gitea.example.com',
'settings.gitea.page.apiBaseUrl.placeholder': 'https://codeberg.org',
'settings.gitea.page.apiBaseUrl.description': 'URL de base par défaut pour les appels API. Pour les instances auto-hébergées, utilisez ladresse de votre serveur, ex. https://gitea.example.com.',
'settings.gitea.page.detectUrls.label': 'URL de détection',
'settings.gitea.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.gitea.page.detectUrls.placeholder': 'codeberg.org',
'settings.gitea.page.detectUrls.description': 'Les dépôts clonés en SSH ou HTTPS depuis ces hôtes sont reconnus comme Gitea ou Forgejo.',
'settings.gitProviders.detectUrls.add': 'Ajouter une URL de détection',
'settings.gitProviders.detectUrls.remove': 'Retirer {host}',
@@ -1715,10 +1715,10 @@ export const settingsDict = {
'settings.github.page.toast.ghCliDisabled': 'gh CLI フォールバックを無効化しました',
'settings.github.page.toast.ghCliUpdateFailed': 'gh CLI 設定の更新に失敗しました',
'settings.github.page.apiBaseUrl.label': 'API ベース URL',
'settings.github.page.apiBaseUrl.placeholder': 'https://github.example.com/api/v3',
'settings.github.page.apiBaseUrl.placeholder': 'https://api.github.com',
'settings.github.page.apiBaseUrl.description': 'API 呼び出しに使用する既定のベース URL です。GitHub Enterprise の場合はサーバーアドレスを指定します。例: https://github.example.com/api/v3。',
'settings.github.page.detectUrls.label': '検出 URL',
'settings.github.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.github.page.detectUrls.placeholder': 'github.com',
'settings.github.page.detectUrls.description': 'これらのホストから SSH または HTTPS でクローンしたリポジトリは GitHub として認識されます。',
'settings.gitlab.page.title': 'GitLab パーソナルアクセストークン',
'settings.gitlab.page.description': 'GitLab パーソナルアクセストークンを貼り付けて接続します。セルフホストの GitLab インスタンスを使用する場合はベース URL を設定してください。',
@@ -1728,10 +1728,10 @@ export const settingsDict = {
'settings.gitlab.page.baseUrl.label': 'ベース URL(任意)',
'settings.gitlab.page.baseUrl.placeholder': 'https://gitlab.com',
'settings.gitlab.page.apiBaseUrl.label': 'API ベース URL',
'settings.gitlab.page.apiBaseUrl.placeholder': 'https://gitlab.example.com',
'settings.gitlab.page.apiBaseUrl.placeholder': 'https://gitlab.com',
'settings.gitlab.page.apiBaseUrl.description': 'API 呼び出しに使用する既定のベース URL です。セルフホストインスタンスの場合はサーバーアドレスを指定します。例: https://gitlab.example.com。',
'settings.gitlab.page.detectUrls.label': '検出 URL',
'settings.gitlab.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.gitlab.page.detectUrls.placeholder': 'gitlab.com',
'settings.gitlab.page.detectUrls.description': 'これらのホストから SSH または HTTPS でクローンしたリポジトリは GitLab として認識されます。',
'settings.gitlab.page.actions.connect': 'GitLab に接続',
'settings.gitlab.page.actions.disconnect': '切断',
@@ -1773,10 +1773,10 @@ export const settingsDict = {
'settings.gitea.page.toast.accountSwitched': 'Gitea アカウントを切り替えました',
'settings.gitea.page.toast.accountSwitchFailed': 'Gitea アカウントの切り替えに失敗しました',
'settings.gitea.page.apiBaseUrl.label': 'API ベース URL',
'settings.gitea.page.apiBaseUrl.placeholder': 'https://gitea.example.com',
'settings.gitea.page.apiBaseUrl.placeholder': 'https://codeberg.org',
'settings.gitea.page.apiBaseUrl.description': 'API 呼び出しに使用する既定のベース URL です。セルフホストインスタンスの場合はサーバーアドレスを指定します。例: https://gitea.example.com。',
'settings.gitea.page.detectUrls.label': '検出 URL',
'settings.gitea.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.gitea.page.detectUrls.placeholder': 'codeberg.org',
'settings.gitea.page.detectUrls.description': 'これらのホストから SSH または HTTPS でクローンしたリポジトリは Gitea または Forgejo として認識されます。',
'settings.gitProviders.detectUrls.add': '検出 URL を追加',
'settings.gitProviders.detectUrls.remove': '{host} を削除',
@@ -1682,10 +1682,10 @@ export const settingsDict = {
'settings.github.page.toast.ghCliDisabled': 'gh CLI 대체 비활성화됨',
'settings.github.page.toast.ghCliUpdateFailed': 'gh CLI 설정을 업데이트하지 못했습니다',
'settings.github.page.apiBaseUrl.label': 'API 기본 URL',
'settings.github.page.apiBaseUrl.placeholder': 'https://github.example.com/api/v3',
'settings.github.page.apiBaseUrl.placeholder': 'https://api.github.com',
'settings.github.page.apiBaseUrl.description': 'API 호출에 사용되는 기본 URL입니다. GitHub Enterprise를 사용하는 경우 서버 주소를 입력하세요(예: https://github.example.com/api/v3).',
'settings.github.page.detectUrls.label': '감지 URL',
'settings.github.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.github.page.detectUrls.placeholder': 'github.com',
'settings.github.page.detectUrls.description': '이 호스트에서 SSH 또는 HTTPS로 복제한 저장소는 GitHub로 인식됩니다.',
'settings.gitlab.page.title': 'GitLab 개인 액세스 토큰',
'settings.gitlab.page.description': '연결하려면 GitLab 개인 액세스 토큰을 붙여넣으세요. 자체 호스팅 GitLab 인스턴스를 사용하는 경우 기본 URL을 설정하세요.',
@@ -1695,10 +1695,10 @@ export const settingsDict = {
'settings.gitlab.page.baseUrl.label': '기본 URL(선택 사항)',
'settings.gitlab.page.baseUrl.placeholder': 'https://gitlab.com',
'settings.gitlab.page.apiBaseUrl.label': 'API 기본 URL',
'settings.gitlab.page.apiBaseUrl.placeholder': 'https://gitlab.example.com',
'settings.gitlab.page.apiBaseUrl.placeholder': 'https://gitlab.com',
'settings.gitlab.page.apiBaseUrl.description': 'API 호출에 사용되는 기본 URL입니다. 자체 호스팅 인스턴스의 경우 서버 주소를 입력하세요(예: https://gitlab.example.com).',
'settings.gitlab.page.detectUrls.label': '감지 URL',
'settings.gitlab.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.gitlab.page.detectUrls.placeholder': 'gitlab.com',
'settings.gitlab.page.detectUrls.description': '이 호스트에서 SSH 또는 HTTPS로 복제한 저장소는 GitLab으로 인식됩니다.',
'settings.gitlab.page.actions.connect': 'GitLab 연결',
'settings.gitlab.page.actions.disconnect': '연결 해제',
@@ -1740,10 +1740,10 @@ export const settingsDict = {
'settings.gitea.page.toast.accountSwitched': 'Gitea 계정이 전환되었습니다',
'settings.gitea.page.toast.accountSwitchFailed': 'Gitea 계정을 전환하지 못했습니다',
'settings.gitea.page.apiBaseUrl.label': 'API 기본 URL',
'settings.gitea.page.apiBaseUrl.placeholder': 'https://gitea.example.com',
'settings.gitea.page.apiBaseUrl.placeholder': 'https://codeberg.org',
'settings.gitea.page.apiBaseUrl.description': 'API 호출에 사용되는 기본 URL입니다. 자체 호스팅 인스턴스의 경우 서버 주소를 입력하세요(예: https://gitea.example.com).',
'settings.gitea.page.detectUrls.label': '감지 URL',
'settings.gitea.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.gitea.page.detectUrls.placeholder': 'codeberg.org',
'settings.gitea.page.detectUrls.description': '이 호스트에서 SSH 또는 HTTPS로 복제한 저장소는 Gitea 또는 Forgejo로 인식됩니다.',
'settings.gitProviders.detectUrls.add': '감지 URL 추가',
'settings.gitProviders.detectUrls.remove': '{host} 제거',
@@ -322,10 +322,10 @@ export const settingsDict = {
'settings.github.page.toast.ghCliDisabled': 'Rezerwa gh CLI wyłączona',
'settings.github.page.toast.ghCliUpdateFailed': 'Nie udało się zaktualizować ustawienia gh CLI',
'settings.github.page.apiBaseUrl.label': 'Adres URL API',
'settings.github.page.apiBaseUrl.placeholder': 'https://github.example.com/api/v3',
'settings.github.page.apiBaseUrl.placeholder': 'https://api.github.com',
'settings.github.page.apiBaseUrl.description': 'Domyślny adres URL dla wywołań API. W przypadku GitHub Enterprise podaj adres swojego serwera, np. https://github.example.com/api/v3.',
'settings.github.page.detectUrls.label': 'Adresy URL wykrywania',
'settings.github.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.github.page.detectUrls.placeholder': 'github.com',
'settings.github.page.detectUrls.description': 'Repozytoria klonowane przez SSH lub HTTPS z tych hostów są rozpoznawane jako GitHub.',
'settings.github.page.oauth.title': 'Token OAuth GitHub',
'settings.github.page.ghCli.title': 'Token CLI GitHub',
@@ -343,10 +343,10 @@ export const settingsDict = {
'settings.gitlab.page.baseUrl.label': 'Podstawowy URL (opcjonalnie)',
'settings.gitlab.page.baseUrl.placeholder': 'https://gitlab.com',
'settings.gitlab.page.apiBaseUrl.label': 'Adres URL API',
'settings.gitlab.page.apiBaseUrl.placeholder': 'https://gitlab.example.com',
'settings.gitlab.page.apiBaseUrl.placeholder': 'https://gitlab.com',
'settings.gitlab.page.apiBaseUrl.description': 'Domyślny adres URL dla wywołań API. W przypadku instancji hostowanych samodzielnie podaj adres swojego serwera, np. https://gitlab.example.com.',
'settings.gitlab.page.detectUrls.label': 'Adresy URL wykrywania',
'settings.gitlab.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.gitlab.page.detectUrls.placeholder': 'gitlab.com',
'settings.gitlab.page.detectUrls.description': 'Repozytoria klonowane przez SSH lub HTTPS z tych hostów są rozpoznawane jako GitLab.',
'settings.gitlab.page.actions.connect': 'Połącz GitLab',
'settings.gitlab.page.actions.disconnect': 'Odłącz',
@@ -388,10 +388,10 @@ export const settingsDict = {
'settings.gitea.page.toast.accountSwitched': 'Konto Gitea zostało przełączone',
'settings.gitea.page.toast.accountSwitchFailed': 'Nie udało się przełączyć konta Gitea',
'settings.gitea.page.apiBaseUrl.label': 'Adres URL API',
'settings.gitea.page.apiBaseUrl.placeholder': 'https://gitea.example.com',
'settings.gitea.page.apiBaseUrl.placeholder': 'https://codeberg.org',
'settings.gitea.page.apiBaseUrl.description': 'Domyślny adres URL dla wywołań API. W przypadku instancji hostowanych samodzielnie podaj adres swojego serwera, np. https://gitea.example.com.',
'settings.gitea.page.detectUrls.label': 'Adresy URL wykrywania',
'settings.gitea.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.gitea.page.detectUrls.placeholder': 'codeberg.org',
'settings.gitea.page.detectUrls.description': 'Repozytoria klonowane przez SSH lub HTTPS z tych hostów są rozpoznawane jako Gitea lub Forgejo.',
'settings.gitProviders.detectUrls.add': 'Dodaj adres URL wykrywania',
'settings.gitProviders.detectUrls.remove': 'Usuń {host}',
@@ -1682,10 +1682,10 @@ export const settingsDict = {
"settings.github.page.toast.ghCliDisabled": "Alternativa gh CLI desativada",
"settings.github.page.toast.ghCliUpdateFailed": "Falha ao atualizar configuração do gh CLI",
"settings.github.page.apiBaseUrl.label": "URL base da API",
"settings.github.page.apiBaseUrl.placeholder": "https://github.example.com/api/v3",
"settings.github.page.apiBaseUrl.placeholder": "https://api.github.com",
"settings.github.page.apiBaseUrl.description": "URL base padrão para chamadas de API. Para GitHub Enterprise, use o endereço do seu servidor, ex.: https://github.example.com/api/v3.",
"settings.github.page.detectUrls.label": "URLs de detecção",
"settings.github.page.detectUrls.placeholder": "ssh://git@git.example.com:2222, https://git.example.com",
"settings.github.page.detectUrls.placeholder": "github.com",
"settings.github.page.detectUrls.description": "Repositórios clonados via SSH ou HTTPS desses hosts são reconhecidos como GitHub.",
"settings.gitlab.page.title": "Token de acesso pessoal do GitLab",
"settings.gitlab.page.description": "Cole um token de acesso pessoal do GitLab para conectar. Defina a URL base ao usar uma instância GitLab auto-hospedada.",
@@ -1695,10 +1695,10 @@ export const settingsDict = {
"settings.gitlab.page.baseUrl.label": "URL base (opcional)",
"settings.gitlab.page.baseUrl.placeholder": "https://gitlab.com",
"settings.gitlab.page.apiBaseUrl.label": "URL base da API",
"settings.gitlab.page.apiBaseUrl.placeholder": "https://gitlab.example.com",
"settings.gitlab.page.apiBaseUrl.placeholder": "https://gitlab.com",
"settings.gitlab.page.apiBaseUrl.description": "URL base padrão para chamadas de API. Para instâncias auto-hospedadas, use o endereço do seu servidor, ex.: https://gitlab.example.com.",
"settings.gitlab.page.detectUrls.label": "URLs de detecção",
"settings.gitlab.page.detectUrls.placeholder": "ssh://git@git.example.com:2222, https://git.example.com",
"settings.gitlab.page.detectUrls.placeholder": "gitlab.com",
"settings.gitlab.page.detectUrls.description": "Repositórios clonados via SSH ou HTTPS desses hosts são reconhecidos como GitLab.",
"settings.gitlab.page.actions.connect": "Conectar GitLab",
"settings.gitlab.page.actions.disconnect": "Desconectar",
@@ -1740,10 +1740,10 @@ export const settingsDict = {
"settings.gitea.page.toast.accountSwitched": "Conta do Gitea alterada",
"settings.gitea.page.toast.accountSwitchFailed": "Falha ao alternar a conta do Gitea",
"settings.gitea.page.apiBaseUrl.label": "URL base da API",
"settings.gitea.page.apiBaseUrl.placeholder": "https://gitea.example.com",
"settings.gitea.page.apiBaseUrl.placeholder": "https://codeberg.org",
"settings.gitea.page.apiBaseUrl.description": "URL base padrão para chamadas de API. Para instâncias auto-hospedadas, use o endereço do seu servidor, ex.: https://gitea.example.com.",
"settings.gitea.page.detectUrls.label": "URLs de detecção",
"settings.gitea.page.detectUrls.placeholder": "ssh://git@git.example.com:2222, https://git.example.com",
"settings.gitea.page.detectUrls.placeholder": "codeberg.org",
"settings.gitea.page.detectUrls.description": "Repositórios clonados via SSH ou HTTPS desses hosts são reconhecidos como Gitea ou Forgejo.",
"settings.gitProviders.detectUrls.add": "Adicionar uma URL de detecção",
"settings.gitProviders.detectUrls.remove": "Remover {host}",
@@ -1682,10 +1682,10 @@ export const settingsDict = {
"settings.github.page.toast.ghCliDisabled": "Резервний варіант gh CLI вимкнено",
"settings.github.page.toast.ghCliUpdateFailed": "Не вдалося оновити налаштування gh CLI",
"settings.github.page.apiBaseUrl.label": "Базова URL-адреса API",
"settings.github.page.apiBaseUrl.placeholder": "https://github.example.com/api/v3",
"settings.github.page.apiBaseUrl.placeholder": "https://api.github.com",
"settings.github.page.apiBaseUrl.description": "Типова базова URL-адреса для викликів API. Для GitHub Enterprise вкажіть адресу свого сервера, напр. https://github.example.com/api/v3.",
"settings.github.page.detectUrls.label": "URL-адреси виявлення",
"settings.github.page.detectUrls.placeholder": "ssh://git@git.example.com:2222, https://git.example.com",
"settings.github.page.detectUrls.placeholder": "github.com",
"settings.github.page.detectUrls.description": "Репозиторії, клоновані через SSH або HTTPS із цих хостів, розпізнаються як GitHub.",
"settings.gitlab.page.title": "Персональний токен доступу GitLab",
"settings.gitlab.page.description": "Вставте персональний токен доступу GitLab, щоб підключитися. Вкажіть базову URL-адресу, якщо використовуєте самостійно розміщену інстанцію GitLab.",
@@ -1695,10 +1695,10 @@ export const settingsDict = {
"settings.gitlab.page.baseUrl.label": "Базова URL-адреса (необов'язково)",
"settings.gitlab.page.baseUrl.placeholder": "https://gitlab.com",
"settings.gitlab.page.apiBaseUrl.label": "Базова URL-адреса API",
"settings.gitlab.page.apiBaseUrl.placeholder": "https://gitlab.example.com",
"settings.gitlab.page.apiBaseUrl.placeholder": "https://gitlab.com",
"settings.gitlab.page.apiBaseUrl.description": "Типова базова URL-адреса для викликів API. Для саморозміщених екземплярів вкажіть адресу свого сервера, напр. https://gitlab.example.com.",
"settings.gitlab.page.detectUrls.label": "URL-адреси виявлення",
"settings.gitlab.page.detectUrls.placeholder": "ssh://git@git.example.com:2222, https://git.example.com",
"settings.gitlab.page.detectUrls.placeholder": "gitlab.com",
"settings.gitlab.page.detectUrls.description": "Репозиторії, клоновані через SSH або HTTPS із цих хостів, розпізнаються як GitLab.",
"settings.gitlab.page.actions.connect": "Підключити GitLab",
"settings.gitlab.page.actions.disconnect": "Відключити",
@@ -1740,10 +1740,10 @@ export const settingsDict = {
"settings.gitea.page.toast.accountSwitched": "Обліковий запис Gitea перемкнено",
"settings.gitea.page.toast.accountSwitchFailed": "Не вдалося перемкнути обліковий запис Gitea",
"settings.gitea.page.apiBaseUrl.label": "Базова URL-адреса API",
"settings.gitea.page.apiBaseUrl.placeholder": "https://gitea.example.com",
"settings.gitea.page.apiBaseUrl.placeholder": "https://codeberg.org",
"settings.gitea.page.apiBaseUrl.description": "Типова базова URL-адреса для викликів API. Для саморозміщених екземплярів вкажіть адресу свого сервера, напр. https://gitea.example.com.",
"settings.gitea.page.detectUrls.label": "URL-адреси виявлення",
"settings.gitea.page.detectUrls.placeholder": "ssh://git@git.example.com:2222, https://git.example.com",
"settings.gitea.page.detectUrls.placeholder": "codeberg.org",
"settings.gitea.page.detectUrls.description": "Репозиторії, клоновані через SSH або HTTPS із цих хостів, розпізнаються як Gitea або Forgejo.",
"settings.gitProviders.detectUrls.add": "Додати URL-адресу виявлення",
"settings.gitProviders.detectUrls.remove": "Видалити {host}",
@@ -1682,10 +1682,10 @@ export const settingsDict = {
'settings.github.page.toast.ghCliDisabled': 'gh CLI 备用已禁用',
'settings.github.page.toast.ghCliUpdateFailed': '更新 gh CLI 设置失败',
'settings.github.page.apiBaseUrl.label': 'API 基础 URL',
'settings.github.page.apiBaseUrl.placeholder': 'https://github.example.com/api/v3',
'settings.github.page.apiBaseUrl.placeholder': 'https://api.github.com',
'settings.github.page.apiBaseUrl.description': 'API 调用的默认基础 URL。GitHub Enterprise 用户可填写服务器地址,例如 https://github.example.com/api/v3。',
'settings.github.page.detectUrls.label': '检测 URL',
'settings.github.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.github.page.detectUrls.placeholder': 'github.com',
'settings.github.page.detectUrls.description': '通过这些主机以 SSH 或 HTTPS 方式克隆的仓库将被识别为 GitHub。',
'settings.gitlab.page.title': 'GitLab 个人访问令牌',
'settings.gitlab.page.description': '粘贴 GitLab 个人访问令牌以连接。使用自托管 GitLab 实例时,请设置基础 URL。',
@@ -1695,10 +1695,10 @@ export const settingsDict = {
'settings.gitlab.page.baseUrl.label': '基础 URL(可选)',
'settings.gitlab.page.baseUrl.placeholder': 'https://gitlab.com',
'settings.gitlab.page.apiBaseUrl.label': 'API 基础 URL',
'settings.gitlab.page.apiBaseUrl.placeholder': 'https://gitlab.example.com',
'settings.gitlab.page.apiBaseUrl.placeholder': 'https://gitlab.com',
'settings.gitlab.page.apiBaseUrl.description': 'API 调用的默认基础 URL。自托管实例请填写服务器地址,例如 https://gitlab.example.com。',
'settings.gitlab.page.detectUrls.label': '检测 URL',
'settings.gitlab.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.gitlab.page.detectUrls.placeholder': 'gitlab.com',
'settings.gitlab.page.detectUrls.description': '通过这些主机以 SSH 或 HTTPS 方式克隆的仓库将被识别为 GitLab。',
'settings.gitlab.page.actions.connect': '连接 GitLab',
'settings.gitlab.page.actions.disconnect': '断开连接',
@@ -1740,10 +1740,10 @@ export const settingsDict = {
'settings.gitea.page.toast.accountSwitched': 'Gitea 账户已切换',
'settings.gitea.page.toast.accountSwitchFailed': '切换 Gitea 账户失败',
'settings.gitea.page.apiBaseUrl.label': 'API 基础 URL',
'settings.gitea.page.apiBaseUrl.placeholder': 'https://gitea.example.com',
'settings.gitea.page.apiBaseUrl.placeholder': 'https://codeberg.org',
'settings.gitea.page.apiBaseUrl.description': 'API 调用的默认基础 URL。自托管实例请填写服务器地址,例如 https://gitea.example.com。',
'settings.gitea.page.detectUrls.label': '检测 URL',
'settings.gitea.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.gitea.page.detectUrls.placeholder': 'codeberg.org',
'settings.gitea.page.detectUrls.description': '通过这些主机以 SSH 或 HTTPS 方式克隆的仓库将被识别为 Gitea 或 Forgejo。',
'settings.gitProviders.detectUrls.add': '添加检测 URL',
'settings.gitProviders.detectUrls.remove': '移除 {host}',
@@ -1589,10 +1589,10 @@ export const settingsDict = {
'settings.github.page.toast.ghCliDisabled': 'gh CLI 備用已停用',
'settings.github.page.toast.ghCliUpdateFailed': '更新 gh CLI 設定失敗',
'settings.github.page.apiBaseUrl.label': 'API 基礎 URL',
'settings.github.page.apiBaseUrl.placeholder': 'https://github.example.com/api/v3',
'settings.github.page.apiBaseUrl.placeholder': 'https://api.github.com',
'settings.github.page.apiBaseUrl.description': 'API 呼叫的預設基礎 URL。GitHub Enterprise 使用者可填寫伺服器位址,例如 https://github.example.com/api/v3。',
'settings.github.page.detectUrls.label': '偵測 URL',
'settings.github.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.github.page.detectUrls.placeholder': 'github.com',
'settings.github.page.detectUrls.description': '透過這些主機以 SSH 或 HTTPS 方式複製的存放庫將識別為 GitHub。',
'settings.gitlab.page.title': 'GitLab 個人存取權杖',
'settings.gitlab.page.description': '貼上 GitLab 個人存取權杖以連線。使用自架 GitLab 執行個體時,請設定基礎 URL。',
@@ -1602,10 +1602,10 @@ export const settingsDict = {
'settings.gitlab.page.baseUrl.label': '基礎 URL(選用)',
'settings.gitlab.page.baseUrl.placeholder': 'https://gitlab.com',
'settings.gitlab.page.apiBaseUrl.label': 'API 基礎 URL',
'settings.gitlab.page.apiBaseUrl.placeholder': 'https://gitlab.example.com',
'settings.gitlab.page.apiBaseUrl.placeholder': 'https://gitlab.com',
'settings.gitlab.page.apiBaseUrl.description': 'API 呼叫的預設基礎 URL。自架執行個體請填寫伺服器位址,例如 https://gitlab.example.com。',
'settings.gitlab.page.detectUrls.label': '偵測 URL',
'settings.gitlab.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.gitlab.page.detectUrls.placeholder': 'gitlab.com',
'settings.gitlab.page.detectUrls.description': '透過這些主機以 SSH 或 HTTPS 方式複製的存放庫將識別為 GitLab。',
'settings.gitlab.page.actions.connect': '連線 GitLab',
'settings.gitlab.page.actions.disconnect': '中斷連線',
@@ -1647,10 +1647,10 @@ export const settingsDict = {
'settings.gitea.page.toast.accountSwitched': 'Gitea 帳號已切換',
'settings.gitea.page.toast.accountSwitchFailed': '切換 Gitea 帳號失敗',
'settings.gitea.page.apiBaseUrl.label': 'API 基礎 URL',
'settings.gitea.page.apiBaseUrl.placeholder': 'https://gitea.example.com',
'settings.gitea.page.apiBaseUrl.placeholder': 'https://codeberg.org',
'settings.gitea.page.apiBaseUrl.description': 'API 呼叫的預設基礎 URL。自架執行個體請填寫伺服器位址,例如 https://gitea.example.com。',
'settings.gitea.page.detectUrls.label': '偵測 URL',
'settings.gitea.page.detectUrls.placeholder': 'ssh://git@git.example.com:2222, https://git.example.com',
'settings.gitea.page.detectUrls.placeholder': 'codeberg.org',
'settings.gitea.page.detectUrls.description': '透過這些主機以 SSH 或 HTTPS 方式複製的存放庫將識別為 Gitea 或 Forgejo。',
'settings.gitProviders.detectUrls.add': '新增偵測 URL',
'settings.gitProviders.detectUrls.remove': '移除 {host}',
@@ -11,12 +11,14 @@
## Public exports
- `GIT_PROVIDER_DEFAULTS`: `{ github: 'https://api.github.com', gitlab: 'https://gitlab.com', gitea: null }`. Built-in defaults are **not persisted**; they are applied at read time by getters.
- `GIT_PROVIDER_DEFAULTS`: `{ github: 'https://api.github.com', gitlab: 'https://gitlab.com', gitea: 'https://codeberg.org' }`. Built-in defaults are **not persisted**; they are applied at read time by getters.
- `GIT_PROVIDER_DEFAULT_DETECT_URLS`: `{ github: ['github.com'], gitlab: ['gitlab.com'], gitea: ['codeberg.org'] }`. Built-in detection hostnames; remotes on these hosts classify as the provider with no configuration (mirrors the client-side built-ins in `packages/ui/src/lib/gitProvider.ts`).
- `normalizeBaseUrl(raw)`: normalize an API base URL (add `https://` when the scheme is missing, strip trailing slashes, preserve subpaths like `/gitlab`), `null` for empty/unparseable input.
- `normalizeDetectionHost(raw)`: extract the bare lowercase hostname from any git remote/URL form (`https://`, `ssh://`, scp-like `git@host:path`, IPv6); mirrors `packages/ui/src/lib/gitHost.ts` `parseGitHost`.
- `sanitizeGitProviders(payload)`: validate/normalize the `gitProviders` shape — only `github|gitlab|gitea` keys survive; `apiBaseUrl` via `normalizeBaseUrl`, `detectUrls` deduped bare hostnames; empty/absent values dropped; returns `undefined` when nothing valid remains.
- `readGitProvidersConfig()`: read the `gitProviders` section from `settings.json` (`OPENCHAMBER_DATA_DIR` env override, else `~/.config/openchamber`); never throws, returns `{}` on missing/invalid data.
- `getProviderApiBaseUrl(provider)`: configured value -> `GIT_PROVIDER_DEFAULTS[provider]` -> `null` (gitea).
- `getProviderApiBaseUrl(provider)`: configured value -> `GIT_PROVIDER_DEFAULTS[provider]` -> `null`.
- `getProviderDetectUrls(provider)`: effective detection hostnames — built-in default hosts plus configured `detectUrls`, deduped (the built-ins always apply).
- `githubWebOriginFromApiBase(apiBase)`: GitHub web origin from an API base — `https://api.github.com` -> `https://github.com`; Enterprise `https://host/api[/v3]` -> `https://host` (trailing `/api`/`/api/v3` stripped, subpath prefixes kept); otherwise the URL origin; never throws, falls back to `https://github.com`.
## Settings shape
@@ -14,7 +14,17 @@ const GIT_PROVIDER_KEYS = ['github', 'gitlab', 'gitea'];
export const GIT_PROVIDER_DEFAULTS = {
github: 'https://api.github.com',
gitlab: 'https://gitlab.com',
gitea: null,
gitea: 'https://codeberg.org',
};
// Built-in detection hostnames: remotes on these hosts are recognized as the
// provider even when the user configures nothing. Configured detectUrls extend
// them — the built-ins always apply (mirrors the client-side detection in
// packages/ui/src/lib/gitProvider.ts).
export const GIT_PROVIDER_DEFAULT_DETECT_URLS = {
github: ['github.com'],
gitlab: ['gitlab.com'],
gitea: ['codeberg.org'],
};
/**
@@ -171,12 +181,23 @@ export function readGitProvidersConfig() {
/**
* Effective API base URL for a provider: the configured settings.json value if
* present, else the built-in default (null for gitea, which has none).
* present, else the built-in default.
*/
export function getProviderApiBaseUrl(provider) {
return readGitProvidersConfig()[provider]?.apiBaseUrl || GIT_PROVIDER_DEFAULTS[provider] || null;
}
/**
* Effective detection hostnames for a provider: the built-in default hosts
* plus any user-configured detectUrls, deduped. The built-ins always apply so
* a default host (e.g. github.com) keeps classifying remotes even when custom
* enterprise hosts are configured.
*/
export function getProviderDetectUrls(provider) {
const configured = readGitProvidersConfig()[provider]?.detectUrls ?? [];
return [...new Set([...(GIT_PROVIDER_DEFAULT_DETECT_URLS[provider] ?? []), ...configured])];
}
/**
* Derive the GitHub web origin from an API base URL. The public API host
* (`https://api.github.com`) maps to `https://github.com`; an Enterprise API
@@ -8,11 +8,13 @@ process.env.OPENCHAMBER_DATA_DIR = TEMP_DATA_DIR;
const {
GIT_PROVIDER_DEFAULTS,
GIT_PROVIDER_DEFAULT_DETECT_URLS,
normalizeBaseUrl,
normalizeDetectionHost,
sanitizeGitProviders,
readGitProvidersConfig,
getProviderApiBaseUrl,
getProviderDetectUrls,
githubWebOriginFromApiBase,
} = await import('./config.js');
@@ -126,7 +128,7 @@ describe('readGitProvidersConfig / getProviderApiBaseUrl', () => {
expect(readGitProvidersConfig()).toEqual({});
expect(getProviderApiBaseUrl('github')).toBe('https://api.github.com');
expect(getProviderApiBaseUrl('gitlab')).toBe('https://gitlab.com');
expect(getProviderApiBaseUrl('gitea')).toBeNull();
expect(getProviderApiBaseUrl('gitea')).toBe('https://codeberg.org');
});
test('reads the configured values from settings.json', () => {
@@ -142,7 +144,7 @@ describe('readGitProvidersConfig / getProviderApiBaseUrl', () => {
});
expect(getProviderApiBaseUrl('github')).toBe('https://github.example.com/api/v3');
expect(getProviderApiBaseUrl('gitlab')).toBe('https://gitlab.example.com');
expect(getProviderApiBaseUrl('gitea')).toBeNull();
expect(getProviderApiBaseUrl('gitea')).toBe('https://codeberg.org');
});
test('never throws on a malformed settings file', () => {
@@ -152,6 +154,26 @@ describe('readGitProvidersConfig / getProviderApiBaseUrl', () => {
});
});
describe('getProviderDetectUrls', () => {
test('returns the built-in default hosts when nothing is configured', () => {
expect(getProviderDetectUrls('github')).toEqual(['github.com']);
expect(getProviderDetectUrls('gitlab')).toEqual(['gitlab.com']);
expect(getProviderDetectUrls('gitea')).toEqual(['codeberg.org']);
expect(GIT_PROVIDER_DEFAULT_DETECT_URLS.gitea).toEqual(['codeberg.org']);
});
test('keeps the built-in hosts and appends configured detectUrls', () => {
fs.writeFileSync(SETTINGS_FILE, JSON.stringify({
gitProviders: {
github: { detectUrls: ['github.example.com'] },
gitea: { detectUrls: ['gitea.example.com', 'codeberg.org'] },
},
}));
expect(getProviderDetectUrls('github')).toEqual(['github.com', 'github.example.com']);
expect(getProviderDetectUrls('gitea')).toEqual(['codeberg.org', 'gitea.example.com']);
});
});
describe('githubWebOriginFromApiBase', () => {
test('maps the public api host to github.com', () => {
expect(githubWebOriginFromApiBase('https://api.github.com')).toBe('https://github.com');
@@ -5,7 +5,7 @@
- This module owns Gitea/Forgejo auth (Personal Access Token), raw REST v1 client access, remote-URL repo resolution, and Gitea issue / pull-request (PR) APIs for OpenChamber, including issue create/update and PR create/update/merge writes.
- From a user perspective, this is the layer that lets the app show Gitea issues and pull requests for a local project, including comments and per-file diffs, and create, edit, and merge pull requests.
- Gitea and Forgejo share the same GitHub-style REST v1 API, so this module serves both. Gitea calls remote work **pull requests** (PR), not merge requests. Gitea repos are flat `owner/repo` — there are no multi-segment namespaces.
- The module mirrors `packages/web/server/lib/gitlab/` (PAT auth + raw-fetch client) but uses a **Personal Access Token** against the `Authorization: token <pat>` header and a **user-supplied base URL** (Gitea is self-hosted; there is no default instance).
- The module mirrors `packages/web/server/lib/gitlab/` (PAT auth + raw-fetch client) but uses a **Personal Access Token** against the `Authorization: token <pat>` header and a **user-supplied base URL** (Gitea is self-hosted; codeberg.org is the only built-in default).
## Entrypoints and structure
@@ -29,8 +29,8 @@
- `clearGiteaAuth()`: remove the current account.
- `normalizeBaseUrl(raw)`: add `https://` when a scheme is missing, strip trailing slash, return `null` for invalid input.
- `GITEA_AUTH_FILE`: auth file path.
- `getGiteaDefaultBaseUrl()`: effective default base URL — configured `gitProviders.gitea.apiBaseUrl` from `settings.json`, else `null`. Used to prefill the connect form and as the connect/status default; stored accounts still require an explicit base URL (there is no invented host).
- There is **no built-in default base URL**: Gitea/Forgejo is self-hosted, so the instance URL is always user-provided.
- `getGiteaDefaultBaseUrl()`: effective default base URL — configured `gitProviders.gitea.apiBaseUrl` from `settings.json`, else `https://codeberg.org`. Used to prefill the connect form and as the connect/status default; stored accounts still require an explicit base URL.
- The only built-in default base URL is **codeberg.org** (a well-known public Forgejo instance); any other Gitea/Forgejo instance URL is user-provided.
### Client (`client.js`)
@@ -47,7 +47,7 @@
- Auth storage: `~/.config/openchamber/gitea-auth.json` (override with `OPENCHAMBER_DATA_DIR`).
- Writes are atomic (tmp file + rename) and file mode is `0o600`.
- Base URL resolution: the caller-supplied `baseUrl` (normalized) is the primary source there is no built-in default instance. A configured `settings.json` `gitProviders.gitea.apiBaseUrl` acts as the connect-form default/fallback. Stored entries without a usable base URL are dropped.
- Base URL resolution: the caller-supplied `baseUrl` (normalized) is the primary source, then the effective default (configured `settings.json` `gitProviders.gitea.apiBaseUrl`, else `https://codeberg.org`). Stored entries without a usable base URL are dropped.
- Account id: `` `${host}:${username}` `` (e.g. `gitea.example.com:alice`), falling back to `token:<first8>` when the username is missing.
- Auth header on every request: `Authorization: token <pat>`.
- Gitea's `GET /user` uses `login`/`full_name`/`html_url`; `setGiteaAuth` accepts both that and the `username`/`web_url` variants.
@@ -92,7 +92,7 @@
| Method | Path | Shape |
|---|---|---|
| GET | `/api/gitea/auth/status` | `{ connected, user?, accounts[], defaultBaseUrl? }` (`defaultBaseUrl` present when connected; the configured `gitProviders.gitea.apiBaseUrl`, else `null`) |
| GET | `/api/gitea/auth/status` | `{ connected, user?, accounts[], defaultBaseUrl? }` (`defaultBaseUrl` present when connected; the effective default — configured `gitProviders.gitea.apiBaseUrl`, else `https://codeberg.org`) |
| POST | `/api/gitea/auth/connect` | body `{ accessToken, baseUrl? }` -> `{ connected, user, accounts }`; `400` for missing/invalid token; `400` when neither a valid `baseUrl` nor a configured default exists |
| POST | `/api/gitea/auth/activate` | body `{ accountId }` -> `{ connected, user, accounts }`; `404` unknown account |
| DELETE | `/api/gitea/auth` | `{ removed }` |
+6 -6
View File
@@ -10,13 +10,13 @@ const OPENCHAMBER_DATA_DIR = process.env.OPENCHAMBER_DATA_DIR
const STORAGE_DIR = OPENCHAMBER_DATA_DIR;
const STORAGE_FILE = path.join(STORAGE_DIR, 'gitea-auth.json');
// Gitea/Forgejo are self-hosted — there is deliberately NO built-in default
// base URL. The instance URL is always user-provided (see `normalizeBaseUrl`);
// auth.js never invents a host for a stored account. A configured
// settings.json gitProviders.gitea.apiBaseUrl can act as the default for the
// connect form, but stored accounts still require an explicit baseUrl.
// Gitea/Forgejo are primarily self-hosted, but codeberg.org (a well-known
// public Forgejo instance) acts as the built-in default base URL. The instance
// URL is always user-provided when connecting to a different host (see
// `normalizeBaseUrl`); auth.js never invents a host for a stored account. A
// configured settings.json gitProviders.gitea.apiBaseUrl overrides the default.
/** Effective default Gitea base URL: configured settings.json value, else null (no built-in default). */
/** Effective default Gitea base URL: configured settings.json value, else codeberg.org. */
export function getGiteaDefaultBaseUrl() {
return getProviderApiBaseUrl('gitea');
}