Gitee

by normal-coder
Verified
  • common
import { getUserAgent } from "universal-user-agent"; import { createGiteeError } from "./errors.js"; import { VERSION } from "./version.js"; // Default Gitee API base URL const DEFAULT_GITEE_API_BASE_URL = "https://gitee.com/api/v5"; /** * Get the Gitee API base URL from environment variables or use the default * @returns The Gitee API base URL */ export function getGiteeApiBaseUrl(): string { return process.env.GITEE_API_BASE_URL || DEFAULT_GITEE_API_BASE_URL; } type RequestOptions = { method?: string; body?: unknown; headers?: Record<string, string>; } async function parseResponseBody(response: Response): Promise<unknown> { const contentType = response.headers.get("content-type"); if (contentType?.includes("application/json")) { return response.json(); } return response.text(); } export function buildUrl(baseUrl: string, params: Record<string, string | number | undefined>): string { const url = new URL(baseUrl); Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { url.searchParams.append(key, value.toString()); } }); return url.toString(); } const USER_AGENT = `modelcontextprotocol/servers/gitee/v${VERSION} ${getUserAgent()}`; // Generate the equivalent curl command for debugging. function generateCurlCommand(url: string, method: string, headers: Record<string, string>, body?: unknown): string { let curl = `curl -X ${method} "${url}"`; // Add request headers Object.entries(headers).forEach(([key, value]) => { curl += ` -H "${key}: ${value}"`; }); // Add request body if (body) { curl += ` -d '${JSON.stringify(body)}'`; } return curl; } // debug utility function export function debug(message: string, data?: unknown): void { if (data !== undefined) { console.error(`[DEBUG] ${message}`, typeof data === 'object' ? JSON.stringify(data, null, 2) : data); } else { console.error(`[DEBUG] ${message}`); } } export async function giteeRequest( urlPath: string, method: string = "GET", body?: unknown, headers?: Record<string, string> ): Promise<unknown> { // Check if the URL is already a full URL or a path let url = urlPath.startsWith("http") ? urlPath : `${getGiteeApiBaseUrl()}${urlPath.startsWith("/") ? urlPath : `/${urlPath}`}`; const requestHeaders: Record<string, string> = { "Accept": "application/json", "Content-Type": "application/json", "User-Agent": USER_AGENT, ...headers, }; if (process.env.GITEE_PERSONAL_ACCESS_TOKEN) { // The Gitee API uses `access_token` as a query parameter or in the `Authorization` header. // Method 1: Add to URL Query Parameters let urlObj = new URL(url); urlObj.searchParams.append('access_token', process.env.GITEE_PERSONAL_ACCESS_TOKEN); url = urlObj.toString(); // Method 2: Add to Request Headers (Two methods are tried to increase success rate) requestHeaders["Authorization"] = `token ${process.env.GITEE_PERSONAL_ACCESS_TOKEN}`; debug(`Using access token: ${process.env.GITEE_PERSONAL_ACCESS_TOKEN.substring(0, 4)}...`); } else { debug(`No access token found in environment variables`); } // Print the request debug(`Request: ${method} ${url}`); debug(`Headers:`, requestHeaders); if (body) { debug(`Body:`, body); } // Print the equivalent curl command const curlCommand = generateCurlCommand(url, method, requestHeaders, body); debug(`cURL: ${curlCommand}\n`); const response = await fetch(url, { method, headers: requestHeaders, body: body ? JSON.stringify(body) : undefined, }); const responseBody = await parseResponseBody(response); // Print the response debug(`Response Status: ${response.status} ${response.statusText}`); debug(`Response Body:`, responseBody); if (!response.ok) { throw createGiteeError(response.status, responseBody); } return responseBody; } export function validateBranchName(branch: string): string { const sanitized = branch.trim(); if (!sanitized) { throw new Error("分支名不能为空"); } if (sanitized.includes("..")) { throw new Error("分支名不能包含 '..'"); } if (/[\s~^:?*[\\\]]/.test(sanitized)) { throw new Error("分支名包含无效字符"); } if (sanitized.startsWith("/") || sanitized.endsWith("/")) { throw new Error("分支名不能以 '/' 开头或结尾"); } if (sanitized.endsWith(".lock")) { throw new Error("分支名不能以 '.lock' 结尾"); } return sanitized; } export function validateRepositoryName(name: string): string { const sanitized = name.trim(); if (!sanitized) { throw new Error("仓库名不能为空"); } if (!/^[a-zA-Z0-9_.-]+$/.test(sanitized)) { throw new Error( "仓库名只能包含字母、数字、连字符、句点和下划线" ); } if (sanitized.startsWith(".") || sanitized.endsWith(".")) { throw new Error("仓库名不能以句点开头或结尾"); } return sanitized; } export function validateOwnerName(owner: string): string { const sanitized = owner.trim(); if (!sanitized) { throw new Error("所有者名称不能为空"); } if (!/^[a-zA-Z0-9][a-zA-Z0-9-]*$/.test(sanitized)) { throw new Error( "所有者名称只能包含字母、数字和连字符,且必须以字母或数字开头" ); } return sanitized; } export async function checkBranchExists( owner: string, repo: string, branch: string ): Promise<boolean> { try { await giteeRequest(`/repos/${owner}/${repo}/branches/${branch}`, "GET"); return true; } catch (error) { if (error && typeof error === "object" && "name" in error && error.name === "GiteeResourceNotFoundError") { return false; } throw error; } } export async function checkUserExists(username: string): Promise<boolean> { try { await giteeRequest(`/users/${username}`, "GET"); return true; } catch (error) { if (error && typeof error === "object" && "name" in error && error.name === "GiteeResourceNotFoundError") { return false; } throw error; } }