Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f41870be1 | |||
| 5423039f89 | |||
| a34b05c55c | |||
| ffc644cab4 | |||
| 4c48a07ef7 | |||
| e3b013b906 | |||
| 89bd770f32 | |||
| a703bc4e10 | |||
| 23e531a042 | |||
| 8fc2beef78 | |||
| 891e46af9b |
27
changelog.md
27
changelog.md
@@ -1,5 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-02-28 - 1.2.0 - feat(giteaclient)
|
||||
add deleteRepo method to delete a repository via Gitea API
|
||||
|
||||
- Implements deleteRepo(owner: string, repo: string): Promise<void>
|
||||
- Uses DELETE /api/v1/repos/{owner}/{repo} and encodes owner and repo with encodeURIComponent
|
||||
|
||||
## 2026-02-28 - 1.1.0 - feat(orgs)
|
||||
add organization and organization-repository APIs: getOrg, getOrgRepos, createOrg, createOrgRepo
|
||||
|
||||
- Added getOrg(orgName): fetch a single organization by name
|
||||
- Added getOrgRepos(orgName, opts?): list repositories in an organization with pagination, sorting by updated, and optional search query
|
||||
- Added createOrg(name, opts?): create a new organization with fullName, description, and visibility (defaults to public)
|
||||
- Added createOrgRepo(orgName, name, opts?): create a repository within an organization; description optional and private defaults to true
|
||||
- Endpoints use encodeURIComponent for orgName in URLs to ensure safe requests
|
||||
|
||||
## 2026-02-24 - 1.0.3 - fix(gitea)
|
||||
no changes detected in the diff; no code or doc updates
|
||||
|
||||
- No files changed in this commit (empty diff).
|
||||
- Current package version in package.json is 1.0.2; no version bump required.
|
||||
|
||||
## 2026-02-24 - 1.0.2 - fix()
|
||||
no code changes to commit — repository unchanged
|
||||
|
||||
- No files were modified according to the provided diff.
|
||||
- No version bump is required; keep current version.
|
||||
|
||||
## 2026-02-24 - 1.0.1 - fix(repo)
|
||||
no changes
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"@git.zone/cli": {
|
||||
"release": {
|
||||
"registries": [
|
||||
"https://verdaccio.lossless.digital",
|
||||
"https://registry.npmjs.org"
|
||||
],
|
||||
"accessLevel": "public"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@apiclient.xyz/gitea",
|
||||
"version": "1.0.1",
|
||||
"version": "1.2.0",
|
||||
"private": false,
|
||||
"description": "A TypeScript client for the Gitea API, providing easy access to repositories, organizations, secrets, and action runs.",
|
||||
"main": "dist_ts/index.js",
|
||||
|
||||
271
readme.md
271
readme.md
@@ -1,30 +1,289 @@
|
||||
# @apiclient.xyz/gitea
|
||||
|
||||
A TypeScript client for the Gitea API, providing easy access to repositories, organizations, secrets, and action runs.
|
||||
A TypeScript client for the Gitea API, providing typed access to repositories, organizations, secrets management, and Gitea Actions workflow runs.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
npm install @apiclient.xyz/gitea
|
||||
# or
|
||||
pnpm install @apiclient.xyz/gitea
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
All examples below use ESM imports and top-level `await`. The package ships as a pure ESM module with full TypeScript type declarations.
|
||||
|
||||
### Creating a Client
|
||||
|
||||
```typescript
|
||||
import { GiteaClient } from '@apiclient.xyz/gitea';
|
||||
|
||||
const client = new GiteaClient('https://gitea.example.com', 'your-api-token');
|
||||
```
|
||||
|
||||
// Test connection
|
||||
The `baseUrl` should point to your Gitea instance root (trailing slashes are stripped automatically). The `token` is an API token generated in Gitea under **Settings > Applications**.
|
||||
|
||||
### Testing the Connection
|
||||
|
||||
```typescript
|
||||
const result = await client.testConnection();
|
||||
|
||||
// List repositories
|
||||
if (result.ok) {
|
||||
console.log('Connected successfully.');
|
||||
} else {
|
||||
console.error('Connection failed:', result.error);
|
||||
}
|
||||
```
|
||||
|
||||
### Listing Repositories
|
||||
|
||||
```typescript
|
||||
// Fetch the first page of repositories (default: 50 per page, sorted by updated)
|
||||
const repos = await client.getRepos();
|
||||
|
||||
// Manage secrets
|
||||
await client.setRepoSecret('owner/repo', 'SECRET_KEY', 'secret-value');
|
||||
// Search with pagination
|
||||
const results = await client.getRepos({
|
||||
search: 'my-project',
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
});
|
||||
|
||||
for (const repo of results) {
|
||||
console.log(`${repo.full_name} - ${repo.description}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Listing Organizations
|
||||
|
||||
```typescript
|
||||
const orgs = await client.getOrgs();
|
||||
|
||||
for (const org of orgs) {
|
||||
console.log(`${org.name} (${org.repo_count} repos)`);
|
||||
}
|
||||
|
||||
// With pagination
|
||||
const page2 = await client.getOrgs({ page: 2, perPage: 10 });
|
||||
```
|
||||
|
||||
### Managing Repository Secrets
|
||||
|
||||
```typescript
|
||||
const ownerRepo = 'my-org/my-repo';
|
||||
|
||||
// List all secrets for a repository
|
||||
const secrets = await client.getRepoSecrets(ownerRepo);
|
||||
for (const secret of secrets) {
|
||||
console.log(`${secret.name} (created ${secret.created_at})`);
|
||||
}
|
||||
|
||||
// Create or update a secret
|
||||
await client.setRepoSecret(ownerRepo, 'DEPLOY_TOKEN', 's3cret-value');
|
||||
|
||||
// Delete a secret
|
||||
await client.deleteRepoSecret(ownerRepo, 'DEPLOY_TOKEN');
|
||||
```
|
||||
|
||||
### Managing Organization Secrets
|
||||
|
||||
```typescript
|
||||
const orgName = 'my-org';
|
||||
|
||||
// List all secrets for an organization
|
||||
const orgSecrets = await client.getOrgSecrets(orgName);
|
||||
|
||||
// Create or update an organization-level secret
|
||||
await client.setOrgSecret(orgName, 'NPM_TOKEN', 'npm_abc123');
|
||||
|
||||
// Delete an organization-level secret
|
||||
await client.deleteOrgSecret(orgName, 'NPM_TOKEN');
|
||||
```
|
||||
|
||||
### Working with Action Runs
|
||||
|
||||
```typescript
|
||||
const ownerRepo = 'my-org/my-repo';
|
||||
|
||||
// List recent action runs
|
||||
const runs = await client.getActionRuns(ownerRepo);
|
||||
for (const run of runs) {
|
||||
console.log(`#${run.id} ${run.name} [${run.status}/${run.conclusion}] on ${run.head_branch}`);
|
||||
}
|
||||
|
||||
// Paginate runs
|
||||
const olderRuns = await client.getActionRuns(ownerRepo, { page: 2, perPage: 10 });
|
||||
|
||||
// Get jobs for a specific run
|
||||
const jobs = await client.getActionRunJobs(ownerRepo, runs[0].id);
|
||||
for (const job of jobs) {
|
||||
console.log(` Job "${job.name}" - ${job.status} (${job.run_duration}s)`);
|
||||
}
|
||||
|
||||
// Fetch raw log output for a job
|
||||
const log = await client.getJobLog(ownerRepo, jobs[0].id);
|
||||
console.log(log);
|
||||
|
||||
// Re-run a workflow
|
||||
await client.rerunAction(ownerRepo, runs[0].id);
|
||||
|
||||
// Cancel a running workflow
|
||||
await client.cancelAction(ownerRepo, runs[0].id);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `GiteaClient`
|
||||
|
||||
| Method | Signature | Returns | Description |
|
||||
|--------|-----------|---------|-------------|
|
||||
| `constructor` | `(baseUrl: string, token: string)` | `GiteaClient` | Create a new client instance. |
|
||||
| `testConnection` | `()` | `Promise<ITestConnectionResult>` | Verify credentials and connectivity. |
|
||||
| `getRepos` | `(opts?: IListOptions)` | `Promise<IGiteaRepository[]>` | Search/list repositories with pagination. |
|
||||
| `getOrgs` | `(opts?: IListOptions)` | `Promise<IGiteaOrganization[]>` | List organizations with pagination. |
|
||||
| `getRepoSecrets` | `(ownerRepo: string)` | `Promise<IGiteaSecret[]>` | List all action secrets for a repository. |
|
||||
| `setRepoSecret` | `(ownerRepo: string, key: string, value: string)` | `Promise<void>` | Create or update a repository secret. |
|
||||
| `deleteRepoSecret` | `(ownerRepo: string, key: string)` | `Promise<void>` | Delete a repository secret. |
|
||||
| `getOrgSecrets` | `(orgName: string)` | `Promise<IGiteaSecret[]>` | List all action secrets for an organization. |
|
||||
| `setOrgSecret` | `(orgName: string, key: string, value: string)` | `Promise<void>` | Create or update an organization secret. |
|
||||
| `deleteOrgSecret` | `(orgName: string, key: string)` | `Promise<void>` | Delete an organization secret. |
|
||||
| `getActionRuns` | `(ownerRepo: string, opts?: IListOptions)` | `Promise<IGiteaActionRun[]>` | List action workflow runs for a repository. |
|
||||
| `getActionRunJobs` | `(ownerRepo: string, runId: number)` | `Promise<IGiteaActionRunJob[]>` | List jobs within a specific action run. |
|
||||
| `getJobLog` | `(ownerRepo: string, jobId: number)` | `Promise<string>` | Fetch the raw log output for a job. |
|
||||
| `rerunAction` | `(ownerRepo: string, runId: number)` | `Promise<void>` | Re-run a completed action run. |
|
||||
| `cancelAction` | `(ownerRepo: string, runId: number)` | `Promise<void>` | Cancel an in-progress action run. |
|
||||
|
||||
### Parameter Conventions
|
||||
|
||||
- **`ownerRepo`** -- Repository identifier in `"owner/repo"` format (e.g., `"my-org/my-repo"`).
|
||||
- **`orgName`** -- Organization login name (e.g., `"my-org"`).
|
||||
- **`key`** -- Secret name, typically uppercase with underscores (e.g., `"DEPLOY_TOKEN"`).
|
||||
- **`runId` / `jobId`** -- Numeric identifiers returned by the list endpoints.
|
||||
|
||||
## Interfaces
|
||||
|
||||
All interfaces are exported from the package entry point and can be imported for type annotations.
|
||||
|
||||
```typescript
|
||||
import type {
|
||||
IGiteaUser,
|
||||
IGiteaRepository,
|
||||
IGiteaOrganization,
|
||||
IGiteaSecret,
|
||||
IGiteaActionRun,
|
||||
IGiteaActionRunJob,
|
||||
ITestConnectionResult,
|
||||
IListOptions,
|
||||
} from '@apiclient.xyz/gitea';
|
||||
```
|
||||
|
||||
### `IListOptions`
|
||||
|
||||
Pagination and search options accepted by list methods.
|
||||
|
||||
```typescript
|
||||
interface IListOptions {
|
||||
search?: string; // Free-text search query (repos only)
|
||||
page?: number; // Page number (default: 1)
|
||||
perPage?: number; // Results per page (default: 50 for repos/orgs, 30 for runs)
|
||||
}
|
||||
```
|
||||
|
||||
### `ITestConnectionResult`
|
||||
|
||||
```typescript
|
||||
interface ITestConnectionResult {
|
||||
ok: boolean; // true if the connection succeeded
|
||||
error?: string; // Error message when ok is false
|
||||
}
|
||||
```
|
||||
|
||||
### `IGiteaUser`
|
||||
|
||||
```typescript
|
||||
interface IGiteaUser {
|
||||
id: number;
|
||||
login: string;
|
||||
full_name: string;
|
||||
email: string;
|
||||
avatar_url: string;
|
||||
}
|
||||
```
|
||||
|
||||
### `IGiteaRepository`
|
||||
|
||||
```typescript
|
||||
interface IGiteaRepository {
|
||||
id: number;
|
||||
name: string;
|
||||
full_name: string; // "owner/repo"
|
||||
description: string;
|
||||
default_branch: string;
|
||||
html_url: string;
|
||||
private: boolean;
|
||||
topics: string[];
|
||||
updated_at: string; // ISO 8601 timestamp
|
||||
owner: {
|
||||
id: number;
|
||||
login: string;
|
||||
avatar_url: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### `IGiteaOrganization`
|
||||
|
||||
```typescript
|
||||
interface IGiteaOrganization {
|
||||
id: number;
|
||||
name: string;
|
||||
full_name: string;
|
||||
description: string;
|
||||
visibility: string; // "public", "limited", or "private"
|
||||
repo_count: number;
|
||||
}
|
||||
```
|
||||
|
||||
### `IGiteaSecret`
|
||||
|
||||
```typescript
|
||||
interface IGiteaSecret {
|
||||
name: string; // Secret key name
|
||||
created_at: string; // ISO 8601 timestamp
|
||||
}
|
||||
```
|
||||
|
||||
### `IGiteaActionRun`
|
||||
|
||||
```typescript
|
||||
interface IGiteaActionRun {
|
||||
id: number;
|
||||
name: string; // Workflow name
|
||||
status: string; // "running", "waiting", "success", "failure", etc.
|
||||
conclusion: string; // Final result: "success", "failure", "cancelled", etc.
|
||||
head_branch: string; // Branch that triggered the run
|
||||
head_sha: string; // Commit SHA
|
||||
html_url: string; // Web URL for the run
|
||||
event: string; // Trigger event: "push", "pull_request", etc.
|
||||
run_duration: number; // Duration in seconds
|
||||
created_at: string; // ISO 8601 timestamp
|
||||
}
|
||||
```
|
||||
|
||||
### `IGiteaActionRunJob`
|
||||
|
||||
```typescript
|
||||
interface IGiteaActionRunJob {
|
||||
id: number;
|
||||
name: string; // Job name from the workflow
|
||||
status: string;
|
||||
conclusion: string;
|
||||
run_duration: number; // Duration in seconds
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
MIT -- see [LICENSE](./license.md) for details.
|
||||
|
||||
Published under the [Lossless GmbH](https://lossless.com) umbrella.
|
||||
|
||||
@@ -7,8 +7,8 @@ const testQenv = new qenv.Qenv('./', '.nogit/');
|
||||
let giteaClient: GiteaClient;
|
||||
|
||||
tap.test('should create a GiteaClient instance', async () => {
|
||||
const baseUrl = testQenv.getEnvVarOnDemand('GITEA_BASE_URL') || 'https://gitea.lossless.digital';
|
||||
const token = testQenv.getEnvVarOnDemand('GITEA_TOKEN') || '';
|
||||
const baseUrl = (await testQenv.getEnvVarOnDemand('GITEA_BASE_URL')) || 'https://gitea.lossless.digital';
|
||||
const token = (await testQenv.getEnvVarOnDemand('GITEA_TOKEN')) || '';
|
||||
giteaClient = new GiteaClient(baseUrl, token);
|
||||
expect(giteaClient).toBeInstanceOf(GiteaClient);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@apiclient.xyz/gitea',
|
||||
version: '1.0.1',
|
||||
version: '1.2.0',
|
||||
description: 'A TypeScript client for the Gitea API, providing easy access to repositories, organizations, secrets, and action runs.'
|
||||
}
|
||||
|
||||
@@ -149,6 +149,56 @@ export class GiteaClient {
|
||||
return this.request<IGiteaOrganization[]>('GET', `/api/v1/orgs?page=${page}&limit=${limit}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single organization by name
|
||||
*/
|
||||
public async getOrg(orgName: string): Promise<IGiteaOrganization> {
|
||||
return this.request<IGiteaOrganization>('GET', `/api/v1/orgs/${encodeURIComponent(orgName)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* List repositories within an organization
|
||||
*/
|
||||
public async getOrgRepos(orgName: string, opts?: IListOptions): Promise<IGiteaRepository[]> {
|
||||
const page = opts?.page || 1;
|
||||
const limit = opts?.perPage || 50;
|
||||
let url = `/api/v1/orgs/${encodeURIComponent(orgName)}/repos?page=${page}&limit=${limit}&sort=updated`;
|
||||
if (opts?.search) {
|
||||
url += `&q=${encodeURIComponent(opts.search)}`;
|
||||
}
|
||||
return this.request<IGiteaRepository[]>('GET', url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new organization
|
||||
*/
|
||||
public async createOrg(name: string, opts?: {
|
||||
fullName?: string;
|
||||
description?: string;
|
||||
visibility?: string;
|
||||
}): Promise<IGiteaOrganization> {
|
||||
return this.request<IGiteaOrganization>('POST', '/api/v1/orgs', {
|
||||
username: name,
|
||||
full_name: opts?.fullName || name,
|
||||
description: opts?.description || '',
|
||||
visibility: opts?.visibility || 'public',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a repository within an organization
|
||||
*/
|
||||
public async createOrgRepo(orgName: string, name: string, opts?: {
|
||||
description?: string;
|
||||
private?: boolean;
|
||||
}): Promise<IGiteaRepository> {
|
||||
return this.request<IGiteaRepository>('POST', `/api/v1/orgs/${encodeURIComponent(orgName)}/repos`, {
|
||||
name,
|
||||
description: opts?.description || '',
|
||||
private: opts?.private ?? true,
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Repository Secrets
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -208,4 +258,15 @@ export class GiteaClient {
|
||||
public async cancelAction(ownerRepo: string, runId: number): Promise<void> {
|
||||
await this.request('POST', `/api/v1/repos/${ownerRepo}/actions/runs/${runId}/cancel`);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Repository Deletion
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
public async deleteRepo(owner: string, repo: string): Promise<void> {
|
||||
await this.request(
|
||||
'DELETE',
|
||||
`/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user