666 lines
22 KiB
TypeScript
666 lines
22 KiB
TypeScript
import * as plugins from './gitlab.plugins.js';
|
|
import type {
|
|
IGitLabUser,
|
|
IGitLabProject,
|
|
IGitLabGroup,
|
|
IGitLabVariable,
|
|
IVariableOptions,
|
|
IGitLabProtectedBranch,
|
|
IGitLabBranch,
|
|
IGitLabTag,
|
|
IGitLabPipeline,
|
|
IGitLabPipelineVariable,
|
|
IGitLabTestReport,
|
|
IGitLabJob,
|
|
ITestConnectionResult,
|
|
IListOptions,
|
|
IPipelineListOptions,
|
|
IJobListOptions,
|
|
} from './gitlab.interfaces.js';
|
|
import { GitLabGroup } from './gitlab.classes.group.js';
|
|
import { GitLabProject } from './gitlab.classes.project.js';
|
|
import { GitLabPipeline } from './gitlab.classes.pipeline.js';
|
|
import { autoPaginate } from './gitlab.helpers.js';
|
|
|
|
export class GitLabClient {
|
|
private baseUrl: string;
|
|
private token: string;
|
|
|
|
constructor(baseUrl: string, token: string) {
|
|
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
|
this.token = token;
|
|
}
|
|
|
|
// ===========================================================================
|
|
// HTTP helpers (internal)
|
|
// ===========================================================================
|
|
|
|
/** @internal */
|
|
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('PRIVATE-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;
|
|
}
|
|
}
|
|
|
|
/** @internal */
|
|
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('PRIVATE-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();
|
|
}
|
|
|
|
/** @internal — multipart form upload (for avatars) */
|
|
async requestMultipart<T = any>(
|
|
method: 'PUT' | 'POST',
|
|
path: string,
|
|
formData: FormData,
|
|
): Promise<T> {
|
|
const url = `${this.baseUrl}${path}`;
|
|
const response = await fetch(url, {
|
|
method,
|
|
headers: { 'PRIVATE-TOKEN': this.token },
|
|
body: formData,
|
|
});
|
|
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;
|
|
}
|
|
}
|
|
|
|
/** @internal — fetch binary data (e.g. avatar images) */
|
|
async requestBinary(url: string): Promise<Uint8Array> {
|
|
const fullUrl = url.startsWith('http') ? url : `${this.baseUrl}${url}`;
|
|
const response = await fetch(fullUrl, {
|
|
headers: { 'PRIVATE-TOKEN': this.token },
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error(`GET ${url}: ${response.status} ${response.statusText}`);
|
|
}
|
|
const buf = await response.arrayBuffer();
|
|
return new Uint8Array(buf);
|
|
}
|
|
|
|
// ===========================================================================
|
|
// Public API — Connection
|
|
// ===========================================================================
|
|
|
|
public async testConnection(): Promise<ITestConnectionResult> {
|
|
try {
|
|
await this.request<IGitLabUser>('GET', '/api/v4/user');
|
|
return { ok: true };
|
|
} catch (err) {
|
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
}
|
|
}
|
|
|
|
// ===========================================================================
|
|
// Public API — Groups (returns rich objects)
|
|
// ===========================================================================
|
|
|
|
/**
|
|
* Get all groups (auto-paginated).
|
|
*/
|
|
public async getGroups(opts?: IListOptions): Promise<GitLabGroup[]> {
|
|
return autoPaginate(
|
|
(page, perPage) => this.requestGetGroups({ ...opts, page, perPage }),
|
|
opts,
|
|
).then(groups => groups.map(g => new GitLabGroup(this, g)));
|
|
}
|
|
|
|
/**
|
|
* Get a single group by full path.
|
|
*/
|
|
public async getGroup(fullPath: string): Promise<GitLabGroup> {
|
|
const raw = await this.requestGetGroupByPath(fullPath);
|
|
return new GitLabGroup(this, raw);
|
|
}
|
|
|
|
/**
|
|
* Create a new group.
|
|
*/
|
|
public async createGroup(name: string, path: string, parentId?: number): Promise<GitLabGroup> {
|
|
const raw = await this.requestCreateGroup(name, path, parentId);
|
|
return new GitLabGroup(this, raw);
|
|
}
|
|
|
|
// ===========================================================================
|
|
// Public API — Projects (returns rich objects)
|
|
// ===========================================================================
|
|
|
|
/**
|
|
* Get all projects (auto-paginated, membership=true).
|
|
*/
|
|
public async getProjects(opts?: IListOptions): Promise<GitLabProject[]> {
|
|
return autoPaginate(
|
|
(page, perPage) => this.requestGetProjects({ ...opts, page, perPage }),
|
|
opts,
|
|
).then(projects => projects.map(p => new GitLabProject(this, p)));
|
|
}
|
|
|
|
/**
|
|
* Get a single project by ID or path.
|
|
*/
|
|
public async getProject(idOrPath: number | string): Promise<GitLabProject> {
|
|
const raw = await this.requestGetProject(idOrPath);
|
|
return new GitLabProject(this, raw);
|
|
}
|
|
|
|
/**
|
|
* Create a new project.
|
|
*/
|
|
public async createProject(name: string, opts?: {
|
|
path?: string;
|
|
namespaceId?: number;
|
|
visibility?: string;
|
|
description?: string;
|
|
}): Promise<GitLabProject> {
|
|
const raw = await this.requestCreateProject(name, opts);
|
|
return new GitLabProject(this, raw);
|
|
}
|
|
|
|
// ===========================================================================
|
|
// Internal request methods — called by domain classes
|
|
// ===========================================================================
|
|
|
|
// --- Groups ---
|
|
|
|
/** @internal */
|
|
async requestGetGroups(opts?: IListOptions): Promise<IGitLabGroup[]> {
|
|
const page = opts?.page || 1;
|
|
const perPage = opts?.perPage || 50;
|
|
let url = `/api/v4/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);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestGetGroupByPath(fullPath: string): Promise<IGitLabGroup> {
|
|
return this.request<IGitLabGroup>('GET', `/api/v4/groups/${encodeURIComponent(fullPath)}`);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestGetGroupProjects(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);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestGetDescendantGroups(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);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestCreateGroup(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);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestUpdateGroup(groupId: number | string, data: Record<string, any>): Promise<void> {
|
|
await this.request('PUT', `/api/v4/groups/${encodeURIComponent(groupId)}`, data);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestSetGroupAvatar(groupId: number | string, imageData: Uint8Array, filename: string): Promise<void> {
|
|
const blob = new Blob([imageData.buffer as ArrayBuffer]);
|
|
const formData = new FormData();
|
|
formData.append('avatar', blob, filename);
|
|
await this.requestMultipart('PUT', `/api/v4/groups/${encodeURIComponent(groupId)}`, formData);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestTransferGroup(groupId: number | string, parentGroupId: number): Promise<void> {
|
|
await this.request('POST', `/api/v4/groups/${encodeURIComponent(groupId)}/transfer`, {
|
|
group_id: parentGroupId,
|
|
});
|
|
}
|
|
|
|
/** @internal */
|
|
async requestDeleteGroup(groupId: number | string): Promise<void> {
|
|
await this.request('DELETE', `/api/v4/groups/${encodeURIComponent(groupId)}`);
|
|
}
|
|
|
|
// --- Projects ---
|
|
|
|
/** @internal */
|
|
async requestGetProjects(opts?: IListOptions): Promise<IGitLabProject[]> {
|
|
const page = opts?.page || 1;
|
|
const perPage = opts?.perPage || 50;
|
|
let url = `/api/v4/projects?membership=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);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestGetProject(idOrPath: number | string): Promise<IGitLabProject> {
|
|
return this.request<IGitLabProject>('GET', `/api/v4/projects/${encodeURIComponent(idOrPath)}`);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestCreateProject(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 || '',
|
|
});
|
|
}
|
|
|
|
/** @internal */
|
|
async requestUpdateProject(projectId: number | string, data: Record<string, any>): Promise<void> {
|
|
await this.request('PUT', `/api/v4/projects/${encodeURIComponent(projectId)}`, data);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestSetProjectAvatar(projectId: number | string, imageData: Uint8Array, filename: string): Promise<void> {
|
|
const blob = new Blob([imageData.buffer as ArrayBuffer]);
|
|
const formData = new FormData();
|
|
formData.append('avatar', blob, filename);
|
|
await this.requestMultipart('PUT', `/api/v4/projects/${encodeURIComponent(projectId)}`, formData);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestTransferProject(projectId: number | string, namespaceId: number): Promise<void> {
|
|
await this.request('PUT', `/api/v4/projects/${encodeURIComponent(projectId)}/transfer`, {
|
|
namespace: namespaceId,
|
|
});
|
|
}
|
|
|
|
/** @internal */
|
|
async requestDeleteProject(projectId: number | string): Promise<void> {
|
|
await this.request('DELETE', `/api/v4/projects/${encodeURIComponent(projectId)}`);
|
|
}
|
|
|
|
// --- Repo Branches & Tags ---
|
|
|
|
/** @internal */
|
|
async requestGetRepoBranches(projectId: number | string, opts?: IListOptions): Promise<IGitLabBranch[]> {
|
|
const page = opts?.page || 1;
|
|
const perPage = opts?.perPage || 50;
|
|
return this.request<IGitLabBranch[]>(
|
|
'GET',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/repository/branches?page=${page}&per_page=${perPage}`,
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestGetRepoTags(projectId: number | string, opts?: IListOptions): Promise<IGitLabTag[]> {
|
|
const page = opts?.page || 1;
|
|
const perPage = opts?.perPage || 50;
|
|
return this.request<IGitLabTag[]>(
|
|
'GET',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/repository/tags?page=${page}&per_page=${perPage}`,
|
|
);
|
|
}
|
|
|
|
// --- Protected Branches ---
|
|
|
|
/** @internal */
|
|
async requestGetProtectedBranches(projectId: number | string): Promise<IGitLabProtectedBranch[]> {
|
|
return this.request<IGitLabProtectedBranch[]>(
|
|
'GET',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/protected_branches`,
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestUnprotectBranch(projectId: number | string, branchName: string): Promise<void> {
|
|
await this.request(
|
|
'DELETE',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/protected_branches/${encodeURIComponent(branchName)}`,
|
|
);
|
|
}
|
|
|
|
// --- Project Variables ---
|
|
|
|
/** @internal */
|
|
async requestGetProjectVariables(projectId: number | string): Promise<IGitLabVariable[]> {
|
|
return this.request<IGitLabVariable[]>(
|
|
'GET',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/variables`,
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestCreateProjectVariable(
|
|
projectId: number | string,
|
|
key: string,
|
|
value: string,
|
|
opts?: IVariableOptions,
|
|
): Promise<IGitLabVariable> {
|
|
return this.request<IGitLabVariable>(
|
|
'POST',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/variables`,
|
|
{
|
|
key,
|
|
value,
|
|
protected: opts?.protected ?? false,
|
|
masked: opts?.masked ?? false,
|
|
environment_scope: opts?.environment_scope ?? '*',
|
|
},
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestUpdateProjectVariable(
|
|
projectId: number | string,
|
|
key: string,
|
|
value: string,
|
|
opts?: IVariableOptions,
|
|
): Promise<IGitLabVariable> {
|
|
const body: any = { value };
|
|
if (opts?.protected !== undefined) body.protected = opts.protected;
|
|
if (opts?.masked !== undefined) body.masked = opts.masked;
|
|
if (opts?.environment_scope !== undefined) body.environment_scope = opts.environment_scope;
|
|
return this.request<IGitLabVariable>(
|
|
'PUT',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/variables/${encodeURIComponent(key)}`,
|
|
body,
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestDeleteProjectVariable(projectId: number | string, key: string): Promise<void> {
|
|
await this.request(
|
|
'DELETE',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/variables/${encodeURIComponent(key)}`,
|
|
);
|
|
}
|
|
|
|
// --- Group Variables ---
|
|
|
|
/** @internal */
|
|
async requestGetGroupVariables(groupId: number | string): Promise<IGitLabVariable[]> {
|
|
return this.request<IGitLabVariable[]>(
|
|
'GET',
|
|
`/api/v4/groups/${encodeURIComponent(groupId)}/variables`,
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestCreateGroupVariable(
|
|
groupId: number | string,
|
|
key: string,
|
|
value: string,
|
|
opts?: IVariableOptions,
|
|
): Promise<IGitLabVariable> {
|
|
return this.request<IGitLabVariable>(
|
|
'POST',
|
|
`/api/v4/groups/${encodeURIComponent(groupId)}/variables`,
|
|
{
|
|
key,
|
|
value,
|
|
protected: opts?.protected ?? false,
|
|
masked: opts?.masked ?? false,
|
|
environment_scope: opts?.environment_scope ?? '*',
|
|
},
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestUpdateGroupVariable(
|
|
groupId: number | string,
|
|
key: string,
|
|
value: string,
|
|
opts?: IVariableOptions,
|
|
): Promise<IGitLabVariable> {
|
|
const body: any = { value };
|
|
if (opts?.protected !== undefined) body.protected = opts.protected;
|
|
if (opts?.masked !== undefined) body.masked = opts.masked;
|
|
if (opts?.environment_scope !== undefined) body.environment_scope = opts.environment_scope;
|
|
return this.request<IGitLabVariable>(
|
|
'PUT',
|
|
`/api/v4/groups/${encodeURIComponent(groupId)}/variables/${encodeURIComponent(key)}`,
|
|
body,
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestDeleteGroupVariable(groupId: number | string, key: string): Promise<void> {
|
|
await this.request(
|
|
'DELETE',
|
|
`/api/v4/groups/${encodeURIComponent(groupId)}/variables/${encodeURIComponent(key)}`,
|
|
);
|
|
}
|
|
|
|
// --- Pipelines ---
|
|
|
|
/** @internal */
|
|
async requestGetPipelines(projectId: number | string, opts?: IPipelineListOptions): Promise<IGitLabPipeline[]> {
|
|
const page = opts?.page || 1;
|
|
const perPage = opts?.perPage || 30;
|
|
const orderBy = opts?.orderBy || 'updated_at';
|
|
const sort = opts?.sort || 'desc';
|
|
let url = `/api/v4/projects/${encodeURIComponent(projectId)}/pipelines?page=${page}&per_page=${perPage}&order_by=${orderBy}&sort=${sort}`;
|
|
if (opts?.status) url += `&status=${encodeURIComponent(opts.status)}`;
|
|
if (opts?.ref) url += `&ref=${encodeURIComponent(opts.ref)}`;
|
|
if (opts?.source) url += `&source=${encodeURIComponent(opts.source)}`;
|
|
if (opts?.scope) url += `&scope=${encodeURIComponent(opts.scope)}`;
|
|
if (opts?.username) url += `&username=${encodeURIComponent(opts.username)}`;
|
|
if (opts?.updatedAfter) url += `&updated_after=${encodeURIComponent(opts.updatedAfter)}`;
|
|
if (opts?.updatedBefore) url += `&updated_before=${encodeURIComponent(opts.updatedBefore)}`;
|
|
return this.request<IGitLabPipeline[]>('GET', url);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestGetPipeline(projectId: number | string, pipelineId: number): Promise<IGitLabPipeline> {
|
|
return this.request<IGitLabPipeline>(
|
|
'GET',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}`,
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestTriggerPipeline(
|
|
projectId: number | string,
|
|
ref: string,
|
|
variables?: { key: string; value: string; variable_type?: string }[],
|
|
): Promise<IGitLabPipeline> {
|
|
const body: any = { ref };
|
|
if (variables && variables.length > 0) {
|
|
body.variables = variables;
|
|
}
|
|
return this.request<IGitLabPipeline>(
|
|
'POST',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/pipeline`,
|
|
body,
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestRetryPipeline(projectId: number | string, pipelineId: number): Promise<IGitLabPipeline> {
|
|
return this.request<IGitLabPipeline>(
|
|
'POST',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/retry`,
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestCancelPipeline(projectId: number | string, pipelineId: number): Promise<IGitLabPipeline> {
|
|
return this.request<IGitLabPipeline>(
|
|
'POST',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/cancel`,
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestDeletePipeline(projectId: number | string, pipelineId: number): Promise<void> {
|
|
await this.request(
|
|
'DELETE',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}`,
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestGetPipelineVariables(projectId: number | string, pipelineId: number): Promise<IGitLabPipelineVariable[]> {
|
|
return this.request<IGitLabPipelineVariable[]>(
|
|
'GET',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/variables`,
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestGetPipelineTestReport(projectId: number | string, pipelineId: number): Promise<IGitLabTestReport> {
|
|
return this.request<IGitLabTestReport>(
|
|
'GET',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/test_report`,
|
|
);
|
|
}
|
|
|
|
// --- Jobs ---
|
|
|
|
/** @internal */
|
|
async requestGetPipelineJobs(projectId: number | string, pipelineId: number, opts?: IJobListOptions): Promise<IGitLabJob[]> {
|
|
const page = opts?.page || 1;
|
|
const perPage = opts?.perPage || 100;
|
|
let url = `/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/jobs?page=${page}&per_page=${perPage}`;
|
|
if (opts?.scope && opts.scope.length > 0) {
|
|
for (const s of opts.scope) {
|
|
url += `&scope[]=${encodeURIComponent(s)}`;
|
|
}
|
|
}
|
|
return this.request<IGitLabJob[]>('GET', url);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestGetJobLog(projectId: number | string, jobId: number): Promise<string> {
|
|
return this.requestText(
|
|
'GET',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/jobs/${jobId}/trace`,
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestRetryJob(projectId: number | string, jobId: number): Promise<IGitLabJob> {
|
|
return this.request<IGitLabJob>(
|
|
'POST',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/jobs/${jobId}/retry`,
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestCancelJob(projectId: number | string, jobId: number): Promise<IGitLabJob> {
|
|
return this.request<IGitLabJob>(
|
|
'POST',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/jobs/${jobId}/cancel`,
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestPlayJob(projectId: number | string, jobId: number): Promise<IGitLabJob> {
|
|
return this.request<IGitLabJob>(
|
|
'POST',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/jobs/${jobId}/play`,
|
|
);
|
|
}
|
|
|
|
/** @internal */
|
|
async requestEraseJob(projectId: number | string, jobId: number): Promise<void> {
|
|
await this.request(
|
|
'POST',
|
|
`/api/v4/projects/${encodeURIComponent(projectId)}/jobs/${jobId}/erase`,
|
|
);
|
|
}
|
|
}
|