Files
gitea/ts/gitea.classes.giteaclient.ts
Juergen Kunz 4bfe5e56bc feat(core): initial implementation of @apiclient.xyz/gitea TypeScript client
Provides GiteaClient class with methods for repos, orgs, secrets, and action runs.
2026-02-24 12:50:02 +00:00

212 lines
7.0 KiB
TypeScript

import * as plugins from './gitea.plugins.js';
import { logger } from './gitea.logging.js';
import type {
IGiteaUser,
IGiteaRepository,
IGiteaOrganization,
IGiteaSecret,
IGiteaActionRun,
IGiteaActionRunJob,
ITestConnectionResult,
IListOptions,
} from './gitea.interfaces.js';
export class GiteaClient {
private baseUrl: string;
private token: string;
constructor(baseUrl: string, token: string) {
// Remove trailing slash if present
this.baseUrl = baseUrl.replace(/\/+$/, '');
this.token = token;
}
// ---------------------------------------------------------------------------
// HTTP helpers
// ---------------------------------------------------------------------------
private async request<T = any>(
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
path: string,
data?: any,
customHeaders?: Record<string, string>,
): Promise<T> {
const url = `${this.baseUrl}${path}`;
let builder = plugins.smartrequest.SmartRequest.create()
.url(url)
.header('Authorization', `token ${this.token}`)
.header('Content-Type', 'application/json');
if (customHeaders) {
for (const [k, v] of Object.entries(customHeaders)) {
builder = builder.header(k, v);
}
}
if (data) {
builder = builder.json(data);
}
let response: Awaited<ReturnType<typeof builder.get>>;
switch (method) {
case 'GET':
response = await builder.get();
break;
case 'POST':
response = await builder.post();
break;
case 'PUT':
response = await builder.put();
break;
case 'DELETE':
response = await builder.delete();
break;
}
if (!response.ok) {
const errorText = await response.text();
throw new Error(`${method} ${path}: ${response.status} ${response.statusText} - ${errorText}`);
}
try {
return await response.json() as T;
} catch {
return undefined as unknown as T;
}
}
private async requestText(
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
path: string,
): Promise<string> {
const url = `${this.baseUrl}${path}`;
let builder = plugins.smartrequest.SmartRequest.create()
.url(url)
.header('Authorization', `token ${this.token}`)
.header('Accept', 'text/plain');
let response: Awaited<ReturnType<typeof builder.get>>;
switch (method) {
case 'GET':
response = await builder.get();
break;
case 'POST':
response = await builder.post();
break;
case 'PUT':
response = await builder.put();
break;
case 'DELETE':
response = await builder.delete();
break;
}
if (!response.ok) {
const errorText = await response.text();
throw new Error(`${method} ${path}: ${response.status} ${response.statusText} - ${errorText}`);
}
return response.text();
}
// ---------------------------------------------------------------------------
// Connection
// ---------------------------------------------------------------------------
public async testConnection(): Promise<ITestConnectionResult> {
try {
await this.request<IGiteaUser>('GET', '/api/v1/user');
return { ok: true };
} catch (err) {
return { ok: false, error: err instanceof Error ? err.message : String(err) };
}
}
// ---------------------------------------------------------------------------
// Repositories
// ---------------------------------------------------------------------------
public async getRepos(opts?: IListOptions): Promise<IGiteaRepository[]> {
const page = opts?.page || 1;
const limit = opts?.perPage || 50;
let url = `/api/v1/repos/search?page=${page}&limit=${limit}&sort=updated`;
if (opts?.search) {
url += `&q=${encodeURIComponent(opts.search)}`;
}
const body = await this.request<any>('GET', url);
return body.data || body;
}
// ---------------------------------------------------------------------------
// Organizations
// ---------------------------------------------------------------------------
public async getOrgs(opts?: IListOptions): Promise<IGiteaOrganization[]> {
const page = opts?.page || 1;
const limit = opts?.perPage || 50;
return this.request<IGiteaOrganization[]>('GET', `/api/v1/orgs?page=${page}&limit=${limit}`);
}
// ---------------------------------------------------------------------------
// Repository Secrets
// ---------------------------------------------------------------------------
public async getRepoSecrets(ownerRepo: string): Promise<IGiteaSecret[]> {
return this.request<IGiteaSecret[]>('GET', `/api/v1/repos/${ownerRepo}/actions/secrets`);
}
public async setRepoSecret(ownerRepo: string, key: string, value: string): Promise<void> {
await this.request('PUT', `/api/v1/repos/${ownerRepo}/actions/secrets/${key}`, { data: value });
}
public async deleteRepoSecret(ownerRepo: string, key: string): Promise<void> {
await this.request('DELETE', `/api/v1/repos/${ownerRepo}/actions/secrets/${key}`);
}
// ---------------------------------------------------------------------------
// Organization Secrets
// ---------------------------------------------------------------------------
public async getOrgSecrets(orgName: string): Promise<IGiteaSecret[]> {
return this.request<IGiteaSecret[]>('GET', `/api/v1/orgs/${orgName}/actions/secrets`);
}
public async setOrgSecret(orgName: string, key: string, value: string): Promise<void> {
await this.request('PUT', `/api/v1/orgs/${orgName}/actions/secrets/${key}`, { data: value });
}
public async deleteOrgSecret(orgName: string, key: string): Promise<void> {
await this.request('DELETE', `/api/v1/orgs/${orgName}/actions/secrets/${key}`);
}
// ---------------------------------------------------------------------------
// Action Runs
// ---------------------------------------------------------------------------
public async getActionRuns(ownerRepo: string, opts?: IListOptions): Promise<IGiteaActionRun[]> {
const page = opts?.page || 1;
const limit = opts?.perPage || 30;
const body = await this.request<any>('GET', `/api/v1/repos/${ownerRepo}/actions/runs?page=${page}&limit=${limit}`);
return body.workflow_runs || body;
}
public async getActionRunJobs(ownerRepo: string, runId: number): Promise<IGiteaActionRunJob[]> {
const body = await this.request<any>('GET', `/api/v1/repos/${ownerRepo}/actions/runs/${runId}/jobs`);
return body.jobs || body;
}
public async getJobLog(ownerRepo: string, jobId: number): Promise<string> {
return this.requestText('GET', `/api/v1/repos/${ownerRepo}/actions/jobs/${jobId}/logs`);
}
public async rerunAction(ownerRepo: string, runId: number): Promise<void> {
await this.request('POST', `/api/v1/repos/${ownerRepo}/actions/runs/${runId}/rerun`);
}
public async cancelAction(ownerRepo: string, runId: number): Promise<void> {
await this.request('POST', `/api/v1/repos/${ownerRepo}/actions/runs/${runId}/cancel`);
}
}