Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | 3x 3x 13x 13x 13x 13x 10x 3x 1x 2x 1x 1x 7x 7x 7x 6x 5x 5x 7x 3x 1x 2x 1x 1x 3x 8x 1x 7x 2x 5x 5x 4x 4x 1x 3x 13x 13x 11x 11x 2x 3x 5x 5x 5x | /**
* Connection testing for init wizard
*/
import { ConnectionTestResult } from './types';
import { enhancedFetch, GitLabTimeoutError } from '../../utils/fetch';
/**
* Test GitLab connection with provided credentials
*/
export async function testConnection(
instanceUrl: string,
token: string,
): Promise<ConnectionTestResult> {
// Normalize URL: strip trailing slash and /api/v4 suffix if present
const baseUrl = instanceUrl.replace(/\/$/, '').replace(/\/api\/v4\/?$/, '');
const apiUrl = `${baseUrl}/api/v4`;
try {
// Test /user endpoint to verify token.
// Timeout is handled by enhancedFetch's Undici dispatcher (connect + headers timeouts)
// rather than a manual AbortController — consistent with all other fetch calls.
const userResponse = await enhancedFetch(`${apiUrl}/user`, {
headers: {
'PRIVATE-TOKEN': token,
Accept: 'application/json',
},
retry: false,
skipAuth: true,
rateLimit: false,
});
if (!userResponse.ok) {
if (userResponse.status === 401) {
return {
success: false,
error: 'Invalid token - authentication failed',
};
}
if (userResponse.status === 403) {
return {
success: false,
error: 'Token lacks required permissions',
};
}
return {
success: false,
error: `GitLab API error: ${userResponse.status} ${userResponse.statusText}`,
};
}
const userData = (await userResponse.json()) as {
username?: string;
email?: string;
is_admin?: boolean;
};
// Get GitLab version
let gitlabVersion: string | undefined;
try {
const versionResponse = await enhancedFetch(`${apiUrl}/version`, {
headers: {
'PRIVATE-TOKEN': token,
Accept: 'application/json',
},
retry: false,
skipAuth: true,
rateLimit: false,
});
if (versionResponse.ok) {
const versionData = (await versionResponse.json()) as { version?: string };
gitlabVersion = versionData.version;
}
} catch {
// Version endpoint may not be available, continue without it
}
return {
success: true,
username: userData.username,
email: userData.email,
isAdmin: userData.is_admin,
gitlabVersion,
};
} catch (error) {
// Handle timeout — doFetch throws GitLabTimeoutError for all Undici timeout types
if (error instanceof GitLabTimeoutError) {
return {
success: false,
error: `Connection timeout - ${instanceUrl} did not respond`,
};
}
// Handle network errors
if (error instanceof TypeError && error.message.includes('fetch')) {
return {
success: false,
error: `Cannot connect to ${instanceUrl} - check URL and network`,
};
}
return {
success: false,
error: error instanceof Error ? error.message : String(error),
};
}
}
/**
* Validate GitLab URL format
*/
export function validateGitLabUrl(url: string): { valid: boolean; error?: string } {
if (!url) {
return { valid: false, error: 'URL is required' };
}
// Must start with https:// or http://
if (!url.startsWith('https://') && !url.startsWith('http://')) {
return { valid: false, error: 'URL must start with https:// or http://' };
}
try {
const parsed = new URL(url);
Iif (!parsed.hostname) {
return { valid: false, error: 'Invalid URL hostname' };
}
return { valid: true };
} catch {
return { valid: false, error: 'Invalid URL format' };
}
}
/**
* Check if URL is GitLab SaaS (gitlab.com)
* Uses strict hostname matching to avoid false positives from URLs like:
* - notgitlab.com (contains "gitlab.com" as substring)
* - gitlab.company.com (contains "gitlab.com" as substring)
*/
export function isGitLabSaas(url: string): boolean {
try {
const parsed = new URL(url);
const hostname = parsed.hostname.toLowerCase();
// Strict match: exactly gitlab.com or subdomain of gitlab.com
return hostname === 'gitlab.com' || hostname.endsWith('.gitlab.com');
} catch {
return false;
}
}
/**
* Generate PAT creation URL for GitLab instance
* Uses least-privilege scopes based on read-only mode
*/
export function getPatCreationUrl(instanceUrl: string, readOnly = false): string {
const baseUrl = instanceUrl.replace(/\/$/, '');
// Use minimal scopes for read-only mode, full api for write access
const scopes = readOnly ? 'read_api,read_user' : 'api,read_user';
return `${baseUrl}/-/user_settings/personal_access_tokens?name=gitlab-mcp&scopes=${scopes}`;
}
|