feat(core): initial implementation of @apiclient.xyz/gitea TypeScript client
Provides GiteaClient class with methods for repos, orgs, secrets, and action runs.
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@apiclient.xyz/gitea',
|
||||
version: '1.0.0',
|
||||
description: 'A TypeScript client for the Gitea API, providing easy access to repositories, organizations, secrets, and action runs.',
|
||||
};
|
||||
@@ -0,0 +1,211 @@
|
||||
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`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
export interface IGiteaUser {
|
||||
id: number;
|
||||
login: string;
|
||||
full_name: string;
|
||||
email: string;
|
||||
avatar_url: string;
|
||||
}
|
||||
|
||||
export interface IGiteaRepository {
|
||||
id: number;
|
||||
name: string;
|
||||
full_name: string;
|
||||
description: string;
|
||||
default_branch: string;
|
||||
html_url: string;
|
||||
private: boolean;
|
||||
topics: string[];
|
||||
updated_at: string;
|
||||
owner: {
|
||||
id: number;
|
||||
login: string;
|
||||
avatar_url: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IGiteaOrganization {
|
||||
id: number;
|
||||
name: string;
|
||||
full_name: string;
|
||||
description: string;
|
||||
visibility: string;
|
||||
repo_count: number;
|
||||
}
|
||||
|
||||
export interface IGiteaSecret {
|
||||
name: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface IGiteaActionRun {
|
||||
id: number;
|
||||
name: string;
|
||||
status: string;
|
||||
conclusion: string;
|
||||
head_branch: string;
|
||||
head_sha: string;
|
||||
html_url: string;
|
||||
event: string;
|
||||
run_duration: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface IGiteaActionRunJob {
|
||||
id: number;
|
||||
name: string;
|
||||
status: string;
|
||||
conclusion: string;
|
||||
run_duration: number;
|
||||
}
|
||||
|
||||
export interface ITestConnectionResult {
|
||||
ok: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface IListOptions {
|
||||
search?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import * as plugins from './gitea.plugins.js';
|
||||
|
||||
export const logger = new plugins.smartlog.ConsoleLog();
|
||||
@@ -0,0 +1,4 @@
|
||||
import * as smartlog from '@push.rocks/smartlog';
|
||||
import * as smartrequest from '@push.rocks/smartrequest';
|
||||
|
||||
export { smartlog, smartrequest };
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
export { GiteaClient } from './gitea.classes.giteaclient.js';
|
||||
export type {
|
||||
IGiteaUser,
|
||||
IGiteaRepository,
|
||||
IGiteaOrganization,
|
||||
IGiteaSecret,
|
||||
IGiteaActionRun,
|
||||
IGiteaActionRunJob,
|
||||
ITestConnectionResult,
|
||||
IListOptions,
|
||||
} from './gitea.interfaces.js';
|
||||
export { commitinfo } from './00_commitinfo_data.js';
|
||||
Reference in New Issue
Block a user