Add typed GitLab instance and API URL settings for GitLab.com and self-managed instances. - Add core GitLab URL defaults, validation, normalization, and project/global inheritance. - Expose project and global GitLab instance/API URL fields in settings with save-split handling. - Cover configuration resolution, settings parity, and settings UI save behavior with tests. - Document the new settings and add a changeset for the published CLI package. Files changed: .changeset/fn-7422-gitlab-configuration.md | 7 ++ docs/settings-reference.md | 6 +- docs/task-management.md | 2 +- packages/core/src/__tests__/gitlab-config.test.ts | 74 +++++++++++++++++++ .../core/src/__tests__/settings-parity.test.ts | 22 +++++- packages/core/src/gitlab-config.ts | 79 +++++++++++++++++++++ packages/core/src/index.ts | 4 +- packages/core/src/settings-schema.ts | 4 ++ packages/core/src/types.ts | 20 ++++++ .../app/__tests__/settings-save-split.test.ts | 53 ++++++++++++++ .../dashboard/app/components/SettingsModal.tsx | 20 ++++++ .../__tests__/SettingsModal.general.test.tsx | 82 ++++++++++++++++++++++ .../app/components/settings/save-split.ts | 6 ++ .../settings/sections/GeneralSection.tsx | 15 ++++ .../settings/sections/GlobalGeneralSection.tsx | 14 ++++ 15 files changed, 402 insertions(+), 6 deletions(-) Fusion-Task-Id: FN-7422 Fusion-Task-Lineage: 0110b4b6-71b0-4a62-afaf-61af65ebef03 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
80 lines
3.0 KiB
TypeScript
80 lines
3.0 KiB
TypeScript
import type { GlobalSettings, ProjectSettings } from "./types.js";
|
|
|
|
export const DEFAULT_GITLAB_INSTANCE_URL = "https://gitlab.com";
|
|
export const DEFAULT_GITLAB_API_BASE_URL = "https://gitlab.com/api/v4";
|
|
|
|
export interface GitlabConfigSettingsSource {
|
|
gitlabInstanceUrl?: string;
|
|
gitlabApiBaseUrl?: string;
|
|
}
|
|
|
|
export interface ResolveGitlabConfigInput {
|
|
project?: GitlabConfigSettingsSource | ProjectSettings | null;
|
|
global?: GitlabConfigSettingsSource | GlobalSettings | null;
|
|
}
|
|
|
|
export interface ResolvedGitlabConfig {
|
|
instanceUrl: string;
|
|
apiBaseUrl: string;
|
|
}
|
|
|
|
function readConfiguredString(value: unknown): string | undefined {
|
|
if (typeof value !== "string") return undefined;
|
|
const trimmed = value.trim();
|
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
}
|
|
|
|
function normalizeHttpUrl(value: string, label: string): string {
|
|
let parsed: URL;
|
|
try {
|
|
parsed = new URL(value);
|
|
} catch {
|
|
throw new Error(`${label} must be a valid absolute http(s) URL`);
|
|
}
|
|
|
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
throw new Error(`${label} must use http:// or https://`);
|
|
}
|
|
if (parsed.username || parsed.password) {
|
|
throw new Error(`${label} must not include username or password userinfo`);
|
|
}
|
|
if (!parsed.hostname) {
|
|
throw new Error(`${label} must include a hostname`);
|
|
}
|
|
|
|
parsed.hash = "";
|
|
parsed.search = "";
|
|
parsed.pathname = parsed.pathname.replace(/\/+$/u, "") || "/";
|
|
const normalized = parsed.toString().replace(/\/$/u, "");
|
|
return normalized;
|
|
}
|
|
|
|
function deriveApiBaseUrl(instanceUrl: string): string {
|
|
const parsed = new URL(instanceUrl);
|
|
const basePath = parsed.pathname.replace(/\/+$/u, "");
|
|
parsed.pathname = `${basePath}/api/v4`.replace(/\/+/gu, "/");
|
|
parsed.search = "";
|
|
parsed.hash = "";
|
|
return parsed.toString().replace(/\/$/u, "");
|
|
}
|
|
|
|
/**
|
|
* FNXC:GitLabConfiguration 2026-07-02-00:00:
|
|
* FN-7422 only establishes typed GitLab.com and self-managed URL configuration for later GitLab auth/import/tracking subtasks. Normalize and validate here before any future network client consumes these settings, preserving self-managed path prefixes while rejecting non-http(s) URLs and userinfo-bearing URLs.
|
|
*/
|
|
export function resolveGitlabConfig(input: ResolveGitlabConfigInput = {}): ResolvedGitlabConfig {
|
|
const projectInstanceUrl = readConfiguredString(input.project?.gitlabInstanceUrl);
|
|
const globalInstanceUrl = readConfiguredString(input.global?.gitlabInstanceUrl);
|
|
const projectApiBaseUrl = readConfiguredString(input.project?.gitlabApiBaseUrl);
|
|
const globalApiBaseUrl = readConfiguredString(input.global?.gitlabApiBaseUrl);
|
|
|
|
const instanceUrl = normalizeHttpUrl(projectInstanceUrl ?? globalInstanceUrl ?? DEFAULT_GITLAB_INSTANCE_URL, "GitLab instance URL");
|
|
const apiBaseUrl = projectApiBaseUrl ?? globalApiBaseUrl
|
|
? normalizeHttpUrl(projectApiBaseUrl ?? globalApiBaseUrl ?? "", "GitLab API base URL")
|
|
: instanceUrl === DEFAULT_GITLAB_INSTANCE_URL
|
|
? DEFAULT_GITLAB_API_BASE_URL
|
|
: deriveApiBaseUrl(instanceUrl);
|
|
|
|
return { instanceUrl, apiBaseUrl };
|
|
}
|