Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ef688115d6 | |||
| 3aaa0f7e68 | |||
| 1cd7c62431 | |||
| 8f85104136 | |||
| c8059fb1c0 | |||
| 413c4be172 |
15
changelog.md
15
changelog.md
@@ -1,5 +1,20 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-02-28 - 2.1.0 - feat(gitlab)
|
||||
add group- and project-management methods to GitLab client
|
||||
|
||||
- Adds getGroupByPath(fullPath) to fetch a group by full path
|
||||
- Adds getGroupProjects(groupId, opts?) to list projects in a group (include_subgroups=true, default perPage=50, optional search)
|
||||
- Adds getDescendantGroups(groupId, opts?) to list descendant subgroups (paginated, default perPage=50, optional search)
|
||||
- Adds createGroup(name, path, parentId?) to create groups (defaults to private visibility)
|
||||
- Adds createProject(name, opts?) to create projects with optional path, namespaceId, visibility (default private) and description
|
||||
|
||||
## 2026-02-24 - 2.0.3 - fix()
|
||||
no changes detected; nothing to commit
|
||||
|
||||
- No files changed in the provided diff
|
||||
- No updates to source, dependencies, or package files
|
||||
|
||||
## 2026-02-24 - 2.0.2 - fix()
|
||||
no changes detected in diff; no version bump required
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@apiclient.xyz/gitlab",
|
||||
"version": "2.0.2",
|
||||
"version": "2.1.0",
|
||||
"private": false,
|
||||
"description": "A TypeScript client for the GitLab API, providing easy access to projects, groups, CI/CD variables, and pipelines.",
|
||||
"main": "dist_ts/index.js",
|
||||
|
||||
278
readme.md
278
readme.md
@@ -4,25 +4,291 @@ A TypeScript client for the GitLab API, providing easy access to projects, group
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
```bash
|
||||
npm install @apiclient.xyz/gitlab
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
All examples below use ESM imports and async/await.
|
||||
|
||||
### Creating a Client
|
||||
|
||||
```typescript
|
||||
import { GitLabClient } from '@apiclient.xyz/gitlab';
|
||||
|
||||
const client = new GitLabClient('https://gitlab.com', 'your-private-token');
|
||||
const client = new GitLabClient('https://gitlab.example.com', 'your-private-token');
|
||||
```
|
||||
|
||||
// Test connection
|
||||
The constructor accepts the base URL of your GitLab instance and a personal access token (or project/group token) used for authentication via the `PRIVATE-TOKEN` header.
|
||||
|
||||
### Testing the Connection
|
||||
|
||||
```typescript
|
||||
const result = await client.testConnection();
|
||||
|
||||
// List projects
|
||||
if (result.ok) {
|
||||
console.log('Connected successfully.');
|
||||
} else {
|
||||
console.error('Connection failed:', result.error);
|
||||
}
|
||||
```
|
||||
|
||||
### Listing Projects
|
||||
|
||||
Retrieve projects you are a member of, ordered by most recently updated.
|
||||
|
||||
```typescript
|
||||
// First page, default 50 per page
|
||||
const projects = await client.getProjects();
|
||||
|
||||
// Manage CI/CD variables
|
||||
await client.createProjectVariable(123, 'SECRET_KEY', 'secret-value');
|
||||
// Search with pagination
|
||||
const filtered = await client.getProjects({
|
||||
search: 'my-service',
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
});
|
||||
|
||||
for (const project of filtered) {
|
||||
console.log(`${project.id} - ${project.path_with_namespace}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Listing Groups
|
||||
|
||||
```typescript
|
||||
const groups = await client.getGroups();
|
||||
|
||||
// Search groups by name
|
||||
const matchingGroups = await client.getGroups({ search: 'platform' });
|
||||
|
||||
for (const group of matchingGroups) {
|
||||
console.log(`${group.id} - ${group.full_path}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Managing Project Variables
|
||||
|
||||
```typescript
|
||||
const projectId = 42;
|
||||
|
||||
// List all variables
|
||||
const vars = await client.getProjectVariables(projectId);
|
||||
|
||||
// Create a variable
|
||||
await client.createProjectVariable(projectId, 'DATABASE_URL', 'postgres://...', {
|
||||
protected: true,
|
||||
masked: true,
|
||||
environment_scope: 'production',
|
||||
});
|
||||
|
||||
// Update a variable
|
||||
await client.updateProjectVariable(projectId, 'DATABASE_URL', 'postgres://new-host/...', {
|
||||
protected: true,
|
||||
});
|
||||
|
||||
// Delete a variable
|
||||
await client.deleteProjectVariable(projectId, 'DATABASE_URL');
|
||||
```
|
||||
|
||||
### Managing Group Variables
|
||||
|
||||
The group variable API mirrors the project variable API.
|
||||
|
||||
```typescript
|
||||
const groupId = 7;
|
||||
|
||||
// List all variables
|
||||
const groupVars = await client.getGroupVariables(groupId);
|
||||
|
||||
// Create a variable
|
||||
await client.createGroupVariable(groupId, 'NPM_TOKEN', 'tok-xxx', {
|
||||
protected: false,
|
||||
masked: true,
|
||||
environment_scope: '*',
|
||||
});
|
||||
|
||||
// Update a variable
|
||||
await client.updateGroupVariable(groupId, 'NPM_TOKEN', 'tok-yyy', {
|
||||
masked: true,
|
||||
});
|
||||
|
||||
// Delete a variable
|
||||
await client.deleteGroupVariable(groupId, 'NPM_TOKEN');
|
||||
```
|
||||
|
||||
### Working with Pipelines
|
||||
|
||||
```typescript
|
||||
const projectId = 42;
|
||||
|
||||
// List recent pipelines
|
||||
const pipelines = await client.getPipelines(projectId, { page: 1, perPage: 10 });
|
||||
|
||||
for (const pipeline of pipelines) {
|
||||
console.log(`Pipeline #${pipeline.id} [${pipeline.status}] on ${pipeline.ref}`);
|
||||
}
|
||||
|
||||
// Get jobs for a specific pipeline
|
||||
const jobs = await client.getPipelineJobs(projectId, pipelines[0].id);
|
||||
for (const job of jobs) {
|
||||
console.log(` Job "${job.name}" (${job.stage}): ${job.status}`);
|
||||
}
|
||||
|
||||
// Read the raw log output of a job
|
||||
const log = await client.getJobLog(projectId, jobs[0].id);
|
||||
console.log(log);
|
||||
|
||||
// Retry a failed pipeline
|
||||
await client.retryPipeline(projectId, pipelines[0].id);
|
||||
|
||||
// Cancel a running pipeline
|
||||
await client.cancelPipeline(projectId, pipelines[0].id);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `GitLabClient`
|
||||
|
||||
| Method | Signature | Returns | Description |
|
||||
|---|---|---|---|
|
||||
| `constructor` | `(baseUrl: string, token: string)` | `GitLabClient` | Create a new client instance. |
|
||||
| `testConnection` | `()` | `Promise<ITestConnectionResult>` | Verify the token and connectivity. |
|
||||
| `getProjects` | `(opts?: IListOptions)` | `Promise<IGitLabProject[]>` | List projects you are a member of. |
|
||||
| `getGroups` | `(opts?: IListOptions)` | `Promise<IGitLabGroup[]>` | List accessible groups. |
|
||||
| `getProjectVariables` | `(projectId: number \| string)` | `Promise<IGitLabVariable[]>` | List all CI/CD variables for a project. |
|
||||
| `createProjectVariable` | `(projectId: number \| string, key: string, value: string, opts?: IVariableOptions)` | `Promise<IGitLabVariable>` | Create a CI/CD variable on a project. |
|
||||
| `updateProjectVariable` | `(projectId: number \| string, key: string, value: string, opts?: IVariableOptions)` | `Promise<IGitLabVariable>` | Update an existing project variable. |
|
||||
| `deleteProjectVariable` | `(projectId: number \| string, key: string)` | `Promise<void>` | Delete a project variable. |
|
||||
| `getGroupVariables` | `(groupId: number \| string)` | `Promise<IGitLabVariable[]>` | List all CI/CD variables for a group. |
|
||||
| `createGroupVariable` | `(groupId: number \| string, key: string, value: string, opts?: IVariableOptions)` | `Promise<IGitLabVariable>` | Create a CI/CD variable on a group. |
|
||||
| `updateGroupVariable` | `(groupId: number \| string, key: string, value: string, opts?: IVariableOptions)` | `Promise<IGitLabVariable>` | Update an existing group variable. |
|
||||
| `deleteGroupVariable` | `(groupId: number \| string, key: string)` | `Promise<void>` | Delete a group variable. |
|
||||
| `getPipelines` | `(projectId: number \| string, opts?: IListOptions)` | `Promise<IGitLabPipeline[]>` | List pipelines for a project, newest first. |
|
||||
| `getPipelineJobs` | `(projectId: number \| string, pipelineId: number)` | `Promise<IGitLabJob[]>` | Get all jobs for a pipeline. |
|
||||
| `getJobLog` | `(projectId: number \| string, jobId: number)` | `Promise<string>` | Retrieve the raw trace/log of a job. |
|
||||
| `retryPipeline` | `(projectId: number \| string, pipelineId: number)` | `Promise<void>` | Retry all failed jobs in a pipeline. |
|
||||
| `cancelPipeline` | `(projectId: number \| string, pipelineId: number)` | `Promise<void>` | Cancel a running pipeline. |
|
||||
|
||||
## Types
|
||||
|
||||
### `IListOptions`
|
||||
|
||||
Pagination and search options used by `getProjects`, `getGroups`, and `getPipelines`.
|
||||
|
||||
```typescript
|
||||
interface IListOptions {
|
||||
search?: string; // Filter results by keyword
|
||||
page?: number; // Page number (default: 1)
|
||||
perPage?: number; // Items per page (default: 50 for projects/groups, 30 for pipelines)
|
||||
}
|
||||
```
|
||||
|
||||
### `IVariableOptions`
|
||||
|
||||
Options when creating or updating CI/CD variables.
|
||||
|
||||
```typescript
|
||||
interface IVariableOptions {
|
||||
protected?: boolean; // Only expose to protected branches/tags (default: false)
|
||||
masked?: boolean; // Mask the value in job logs (default: false)
|
||||
environment_scope?: string; // Environment scope (default: '*' for all environments)
|
||||
}
|
||||
```
|
||||
|
||||
### `ITestConnectionResult`
|
||||
|
||||
```typescript
|
||||
interface ITestConnectionResult {
|
||||
ok: boolean;
|
||||
error?: string; // Present when ok is false
|
||||
}
|
||||
```
|
||||
|
||||
### `IGitLabProject`
|
||||
|
||||
```typescript
|
||||
interface IGitLabProject {
|
||||
id: number;
|
||||
name: string;
|
||||
path_with_namespace: string;
|
||||
description: string;
|
||||
default_branch: string;
|
||||
web_url: string;
|
||||
visibility: string;
|
||||
topics: string[];
|
||||
last_activity_at: string;
|
||||
}
|
||||
```
|
||||
|
||||
### `IGitLabGroup`
|
||||
|
||||
```typescript
|
||||
interface IGitLabGroup {
|
||||
id: number;
|
||||
name: string;
|
||||
full_path: string;
|
||||
description: string;
|
||||
web_url: string;
|
||||
visibility: string;
|
||||
}
|
||||
```
|
||||
|
||||
### `IGitLabVariable`
|
||||
|
||||
```typescript
|
||||
interface IGitLabVariable {
|
||||
key: string;
|
||||
value: string;
|
||||
variable_type: string;
|
||||
protected: boolean;
|
||||
masked: boolean;
|
||||
environment_scope: string;
|
||||
}
|
||||
```
|
||||
|
||||
### `IGitLabPipeline`
|
||||
|
||||
```typescript
|
||||
interface IGitLabPipeline {
|
||||
id: number;
|
||||
project_id: number;
|
||||
status: string;
|
||||
ref: string;
|
||||
sha: string;
|
||||
web_url: string;
|
||||
duration: number;
|
||||
created_at: string;
|
||||
source: string;
|
||||
}
|
||||
```
|
||||
|
||||
### `IGitLabJob`
|
||||
|
||||
```typescript
|
||||
interface IGitLabJob {
|
||||
id: number;
|
||||
name: string;
|
||||
stage: string;
|
||||
status: string;
|
||||
duration: number;
|
||||
}
|
||||
```
|
||||
|
||||
### `IGitLabUser`
|
||||
|
||||
Returned internally by `testConnection` to verify credentials.
|
||||
|
||||
```typescript
|
||||
interface IGitLabUser {
|
||||
id: number;
|
||||
username: string;
|
||||
name: string;
|
||||
email: string;
|
||||
avatar_url: string;
|
||||
web_url: string;
|
||||
state: string;
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
@@ -7,8 +7,8 @@ const testQenv = new qenv.Qenv('./', '.nogit/');
|
||||
let gitlabClient: GitLabClient;
|
||||
|
||||
tap.test('should create a GitLabClient instance', async () => {
|
||||
const baseUrl = testQenv.getEnvVarOnDemand('GITLAB_BASE_URL') || 'https://gitlab.com';
|
||||
const token = testQenv.getEnvVarOnDemand('GITLAB_TOKEN') || '';
|
||||
const baseUrl = (await testQenv.getEnvVarOnDemand('GITLAB_BASE_URL')) || 'https://gitlab.com';
|
||||
const token = (await testQenv.getEnvVarOnDemand('GITLAB_TOKEN')) || '';
|
||||
gitlabClient = new GitLabClient(baseUrl, token);
|
||||
expect(gitlabClient).toBeInstanceOf(GitLabClient);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@apiclient.xyz/gitlab',
|
||||
version: '2.0.2',
|
||||
version: '2.1.0',
|
||||
description: 'A TypeScript client for the GitLab API, providing easy access to projects, groups, CI/CD variables, and pipelines.'
|
||||
}
|
||||
|
||||
@@ -125,6 +125,73 @@ export class GitLabClient {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Groups — scoped queries
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get a single group by its full path (e.g. "foss.global" or "foss.global/push.rocks")
|
||||
*/
|
||||
public async getGroupByPath(fullPath: string): Promise<IGitLabGroup> {
|
||||
return this.request<IGitLabGroup>(
|
||||
'GET',
|
||||
`/api/v4/groups/${encodeURIComponent(fullPath)}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* List projects within a group (includes subgroups when include_subgroups=true)
|
||||
*/
|
||||
public async getGroupProjects(groupId: number | string, opts?: IListOptions): Promise<IGitLabProject[]> {
|
||||
const page = opts?.page || 1;
|
||||
const perPage = opts?.perPage || 50;
|
||||
let url = `/api/v4/groups/${encodeURIComponent(groupId)}/projects?include_subgroups=true&order_by=updated_at&sort=desc&page=${page}&per_page=${perPage}`;
|
||||
if (opts?.search) {
|
||||
url += `&search=${encodeURIComponent(opts.search)}`;
|
||||
}
|
||||
return this.request<IGitLabProject[]>('GET', url);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all descendant groups (recursive subgroups) within a group
|
||||
*/
|
||||
public async getDescendantGroups(groupId: number | string, opts?: IListOptions): Promise<IGitLabGroup[]> {
|
||||
const page = opts?.page || 1;
|
||||
const perPage = opts?.perPage || 50;
|
||||
let url = `/api/v4/groups/${encodeURIComponent(groupId)}/descendant_groups?order_by=name&sort=asc&page=${page}&per_page=${perPage}`;
|
||||
if (opts?.search) {
|
||||
url += `&search=${encodeURIComponent(opts.search)}`;
|
||||
}
|
||||
return this.request<IGitLabGroup[]>('GET', url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new group. Optionally nested under a parent group.
|
||||
*/
|
||||
public async createGroup(name: string, path: string, parentId?: number): Promise<IGitLabGroup> {
|
||||
const body: any = { name, path, visibility: 'private' };
|
||||
if (parentId) body.parent_id = parentId;
|
||||
return this.request<IGitLabGroup>('POST', '/api/v4/groups', body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new project (repository).
|
||||
*/
|
||||
public async createProject(name: string, opts?: {
|
||||
path?: string;
|
||||
namespaceId?: number;
|
||||
visibility?: string;
|
||||
description?: string;
|
||||
}): Promise<IGitLabProject> {
|
||||
return this.request<IGitLabProject>('POST', '/api/v4/projects', {
|
||||
name,
|
||||
path: opts?.path || name,
|
||||
namespace_id: opts?.namespaceId,
|
||||
visibility: opts?.visibility || 'private',
|
||||
description: opts?.description || '',
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Projects
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user