Add a settings-controlled GitLab enablement path while preserving saved GitLab configuration. - Add project and global GitLab enable settings with collapsible Settings UI for URL and token controls. - Gate GitLab auth, import loading, and import actions when GitLab integration is disabled. - Preserve global GitLab values separately during global settings saves and document the new behavior. - Cover enablement resolution, settings save behavior, disabled import UI, and GitLab route/auth gating with tests. Files changed: .changeset/fn-7453-gitlab-enable-disclosure.md | 7 ++ docs/dashboard-guide.md | 7 ++ docs/gitlab-parity-inventory.md | 1 + docs/settings-reference.md | 4 ++ docs/task-management.md | 6 +- packages/core/src/__tests__/gitlab-config.test.ts | 17 ++++- .../core/src/__tests__/settings-parity.test.ts | 18 +++-- packages/core/src/gitlab-config.ts | 15 ++++- packages/core/src/index.ts | 2 +- packages/core/src/settings-schema.ts | 2 + packages/core/src/types.ts | 5 ++ .../app/__tests__/settings-save-split.test.ts | 20 +++--- .../dashboard/app/components/GitHubImportModal.tsx | 43 +++++++++--- .../dashboard/app/components/SettingsModal.css | 77 +++++++++++++++++++++ .../dashboard/app/components/SettingsModal.tsx | 34 ++++++++- .../__tests__/GitHubImportModal.test.tsx | 19 ++++++ .../__tests__/SettingsModal.general.test.tsx | 78 ++++++++++++++++++++++ .../SettingsModal.scheduling-merge.test.tsx | 7 +- .../app/components/settings/save-split.ts | 5 +- .../settings/sections/GeneralSection.tsx | 36 ++++++---- .../settings/sections/GlobalGeneralSection.tsx | 71 ++++++++++++-------- .../components/settings/sections/MergeSection.tsx | 44 +++++++----- .../dashboard/src/__tests__/gitlab-auth.test.ts | 20 ++++++ .../dashboard/src/__tests__/routes-gitlab.test.ts | 11 +++ packages/dashboard/src/gitlab-auth.ts | 11 ++- 25 files changed, 458 insertions(+), 102 deletions(-) Fusion-Task-Id: FN-7453 Fusion-Task-Lineage: 81fbd39d-675f-49d6-8d13-b7fcb4d25658 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
93 lines
3.7 KiB
TypeScript
93 lines
3.7 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 {
|
|
gitlabEnabled?: boolean;
|
|
gitlabInstanceUrl?: string;
|
|
gitlabApiBaseUrl?: string;
|
|
}
|
|
|
|
export interface ResolveGitlabConfigInput {
|
|
project?: GitlabConfigSettingsSource | ProjectSettings | null;
|
|
global?: GitlabConfigSettingsSource | GlobalSettings | null;
|
|
}
|
|
|
|
export interface ResolvedGitlabConfig {
|
|
enabled: boolean;
|
|
instanceUrl: string;
|
|
apiBaseUrl: string;
|
|
}
|
|
|
|
export function resolveGitlabEnabled(input: ResolveGitlabConfigInput = {}): boolean {
|
|
/*
|
|
FNXC:GitLabEnablement 2026-07-02-00:00:
|
|
FN-7453 separates saved GitLab URL/token configuration from whether GitLab integrations are active. Undefined remains effectively enabled for backward compatibility; explicit project false overrides global true/undefined and short-circuits runtime network paths before URL or token validation.
|
|
*/
|
|
if (typeof input.project?.gitlabEnabled === "boolean") return input.project.gitlabEnabled;
|
|
if (typeof input.global?.gitlabEnabled === "boolean") return input.global.gitlabEnabled;
|
|
return true;
|
|
}
|
|
|
|
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 enabled = resolveGitlabEnabled(input);
|
|
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 { enabled, instanceUrl, apiBaseUrl };
|
|
}
|