@zereight/mcp-gitlab 2.0.2 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/{src/customSchemas.js → customSchemas.js} +12 -0
- package/build/index.js +4079 -211
- package/build/{src/schemas.js → schemas.js} +0 -1
- package/package.json +3 -8
- package/build/src/argon2wrapper.js +0 -68
- package/build/src/authentication.js +0 -78
- package/build/src/authhelpers.js +0 -44
- package/build/src/config.js +0 -99
- package/build/src/gitlabhandler.js +0 -1666
- package/build/src/gitlabsession.js +0 -103
- package/build/src/logger.js +0 -11
- package/build/src/mcpserver.js +0 -1344
- package/build/src/oauth.js +0 -389
|
@@ -1,1666 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { URL } from "url";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import { config } from "./config.js";
|
|
6
|
-
import { GitLabForkSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabIssueSchema, GitLabMergeRequestSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabSearchResponseSchema, GitLabTreeSchema, GitLabCommitSchema, GitLabNamespaceSchema, GitLabNamespaceExistsResponseSchema, GitLabProjectSchema, GitLabUserSchema, GitLabUsersResponseSchema, GitLabDiffSchema, GitLabIssueLinkSchema, GitLabIssueWithLinkDetailsSchema, GitLabDiscussionNoteSchema, GitLabDiscussionSchema, PaginatedDiscussionsResponseSchema, GitLabWikiPageSchema, GitLabTreeItemSchema, GitLabPipelineSchema, GitLabPipelineJobSchema, GitLabMilestonesSchema, GitLabCompareResultSchema, GitLabDraftNoteSchema, GitLabProjectMemberSchema, GitLabMarkdownUploadSchema, } from "./schemas.js";
|
|
7
|
-
import { GitlabSession } from "./gitlabsession.js";
|
|
8
|
-
export class GitlabHandler extends GitlabSession {
|
|
9
|
-
/**
|
|
10
|
-
* Utility function for handling GitLab API errors
|
|
11
|
-
*/
|
|
12
|
-
async handleGitLabError(response) {
|
|
13
|
-
if (!response.ok) {
|
|
14
|
-
const errorBody = await response.text();
|
|
15
|
-
// Check specifically for Rate Limit error
|
|
16
|
-
if (response.status === 403 && errorBody.includes("User API Key Rate limit exceeded")) {
|
|
17
|
-
console.error("GitLab API Rate Limit Exceeded:", errorBody);
|
|
18
|
-
console.log("User API Key Rate limit exceeded. Please try again later.");
|
|
19
|
-
throw new Error(`GitLab API Rate Limit Exceeded: ${errorBody}`);
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
// Handle other API errors
|
|
23
|
-
throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Get effective project ID based on config or provided ID
|
|
29
|
-
*/
|
|
30
|
-
getEffectiveProjectId(projectId) {
|
|
31
|
-
return config.GITLAB_PROJECT_ID || projectId;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Create a fork of a GitLab project
|
|
35
|
-
*/
|
|
36
|
-
async forkProject(projectId, namespace) {
|
|
37
|
-
projectId = decodeURIComponent(projectId);
|
|
38
|
-
const effectiveProjectId = this.getEffectiveProjectId(projectId);
|
|
39
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/fork`);
|
|
40
|
-
if (namespace) {
|
|
41
|
-
url.searchParams.append("namespace", namespace);
|
|
42
|
-
}
|
|
43
|
-
const response = await this.fetch(url.toString(), {
|
|
44
|
-
method: "POST",
|
|
45
|
-
});
|
|
46
|
-
if (response.status === 409) {
|
|
47
|
-
throw new Error("Project already exists in the target namespace");
|
|
48
|
-
}
|
|
49
|
-
await this.handleGitLabError(response);
|
|
50
|
-
const data = await response.json();
|
|
51
|
-
return GitLabForkSchema.parse(data);
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Create a new branch in a GitLab project
|
|
55
|
-
*/
|
|
56
|
-
async createBranch(projectId, options) {
|
|
57
|
-
projectId = decodeURIComponent(projectId);
|
|
58
|
-
const effectiveProjectId = this.getEffectiveProjectId(projectId);
|
|
59
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/repository/branches`);
|
|
60
|
-
const response = await this.fetch(url.toString(), {
|
|
61
|
-
method: "POST",
|
|
62
|
-
body: JSON.stringify({
|
|
63
|
-
branch: options.name,
|
|
64
|
-
ref: options.ref,
|
|
65
|
-
}),
|
|
66
|
-
});
|
|
67
|
-
await this.handleGitLabError(response);
|
|
68
|
-
return GitLabReferenceSchema.parse(await response.json());
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Get the default branch for a GitLab project
|
|
72
|
-
*/
|
|
73
|
-
async getDefaultBranchRef(projectId) {
|
|
74
|
-
projectId = decodeURIComponent(projectId);
|
|
75
|
-
const effectiveProjectId = this.getEffectiveProjectId(projectId);
|
|
76
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}`);
|
|
77
|
-
const response = await this.fetch(url.toString(), {});
|
|
78
|
-
await this.handleGitLabError(response);
|
|
79
|
-
const project = GitLabRepositorySchema.parse(await response.json());
|
|
80
|
-
return project.default_branch ?? "main";
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Get the contents of a file from a GitLab project
|
|
84
|
-
*/
|
|
85
|
-
async getFileContents(projectId, filePath, ref) {
|
|
86
|
-
projectId = decodeURIComponent(projectId);
|
|
87
|
-
const effectiveProjectId = this.getEffectiveProjectId(projectId);
|
|
88
|
-
const encodedPath = encodeURIComponent(filePath);
|
|
89
|
-
if (!ref) {
|
|
90
|
-
ref = await this.getDefaultBranchRef(projectId);
|
|
91
|
-
}
|
|
92
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/repository/files/${encodedPath}`);
|
|
93
|
-
url.searchParams.append("ref", ref);
|
|
94
|
-
const response = await this.fetch(url.toString(), {});
|
|
95
|
-
if (response.status === 404) {
|
|
96
|
-
throw new Error(`File not found: ${filePath}`);
|
|
97
|
-
}
|
|
98
|
-
await this.handleGitLabError(response);
|
|
99
|
-
const data = await response.json();
|
|
100
|
-
const parsedData = GitLabContentSchema.parse(data);
|
|
101
|
-
// Decode Base64 content to UTF-8
|
|
102
|
-
if (!Array.isArray(parsedData) && parsedData.content) {
|
|
103
|
-
parsedData.content = Buffer.from(parsedData.content, "base64").toString("utf8");
|
|
104
|
-
parsedData.encoding = "utf8";
|
|
105
|
-
}
|
|
106
|
-
return parsedData;
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Create a new issue in a GitLab project
|
|
110
|
-
*/
|
|
111
|
-
async createIssue(projectId, options) {
|
|
112
|
-
projectId = decodeURIComponent(projectId);
|
|
113
|
-
const effectiveProjectId = this.getEffectiveProjectId(projectId);
|
|
114
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/issues`);
|
|
115
|
-
const response = await this.fetch(url.toString(), {
|
|
116
|
-
method: "POST",
|
|
117
|
-
body: JSON.stringify({
|
|
118
|
-
title: options.title,
|
|
119
|
-
description: options.description,
|
|
120
|
-
assignee_ids: options.assignee_ids,
|
|
121
|
-
milestone_id: options.milestone_id,
|
|
122
|
-
labels: options.labels?.join(","),
|
|
123
|
-
}),
|
|
124
|
-
});
|
|
125
|
-
if (response.status === 400) {
|
|
126
|
-
const errorBody = await response.text();
|
|
127
|
-
throw new Error(`Invalid request: ${errorBody}`);
|
|
128
|
-
}
|
|
129
|
-
await this.handleGitLabError(response);
|
|
130
|
-
const data = await response.json();
|
|
131
|
-
return GitLabIssueSchema.parse(data);
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* List issues in a GitLab project
|
|
135
|
-
*/
|
|
136
|
-
async listIssues(projectId, options = {}) {
|
|
137
|
-
projectId = decodeURIComponent(projectId);
|
|
138
|
-
const effectiveProjectId = this.getEffectiveProjectId(projectId);
|
|
139
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/issues`);
|
|
140
|
-
Object.entries(options).forEach(([key, value]) => {
|
|
141
|
-
if (value !== undefined && value !== null) {
|
|
142
|
-
const keys = ["labels", "assignee_username"];
|
|
143
|
-
if (keys.includes(key)) {
|
|
144
|
-
if (Array.isArray(value)) {
|
|
145
|
-
value.forEach(label => {
|
|
146
|
-
url.searchParams.append(`${key}[]`, label.toString());
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
url.searchParams.append(`${key}[]`, value.toString());
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
url.searchParams.append(key, value.toString());
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
const response = await this.fetch(url.toString(), {});
|
|
159
|
-
await this.handleGitLabError(response);
|
|
160
|
-
const data = await response.json();
|
|
161
|
-
return z.array(GitLabIssueSchema).parse(data);
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* List merge requests in a GitLab project with optional filtering
|
|
165
|
-
*/
|
|
166
|
-
async listMergeRequests(projectId, options = {}) {
|
|
167
|
-
projectId = decodeURIComponent(projectId);
|
|
168
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests`);
|
|
169
|
-
Object.entries(options).forEach(([key, value]) => {
|
|
170
|
-
if (value !== undefined) {
|
|
171
|
-
if (key === "labels" && Array.isArray(value)) {
|
|
172
|
-
url.searchParams.append(key, value.join(","));
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
url.searchParams.append(key, value.toString());
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
const response = await this.fetch(url.toString(), {});
|
|
180
|
-
await this.handleGitLabError(response);
|
|
181
|
-
const data = await response.json();
|
|
182
|
-
return z.array(GitLabMergeRequestSchema).parse(data);
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Get a single issue from a GitLab project
|
|
186
|
-
*/
|
|
187
|
-
async getIssue(projectId, issueIid) {
|
|
188
|
-
projectId = decodeURIComponent(projectId);
|
|
189
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/issues/${issueIid}`);
|
|
190
|
-
const response = await this.fetch(url.toString(), {});
|
|
191
|
-
await this.handleGitLabError(response);
|
|
192
|
-
const data = await response.json();
|
|
193
|
-
return GitLabIssueSchema.parse(data);
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Update an issue in a GitLab project
|
|
197
|
-
*/
|
|
198
|
-
async updateIssue(projectId, issueIid, options) {
|
|
199
|
-
projectId = decodeURIComponent(projectId);
|
|
200
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/issues/${issueIid}`);
|
|
201
|
-
const body = { ...options };
|
|
202
|
-
if (body.labels && Array.isArray(body.labels)) {
|
|
203
|
-
body.labels = body.labels.join(",");
|
|
204
|
-
}
|
|
205
|
-
const response = await this.fetch(url.toString(), {
|
|
206
|
-
method: "PUT",
|
|
207
|
-
body: JSON.stringify(body),
|
|
208
|
-
});
|
|
209
|
-
await this.handleGitLabError(response);
|
|
210
|
-
const data = await response.json();
|
|
211
|
-
return GitLabIssueSchema.parse(data);
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Delete an issue from a GitLab project
|
|
215
|
-
*/
|
|
216
|
-
async deleteIssue(projectId, issueIid) {
|
|
217
|
-
projectId = decodeURIComponent(projectId);
|
|
218
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/issues/${issueIid}`);
|
|
219
|
-
const response = await this.fetch(url.toString(), {
|
|
220
|
-
method: "DELETE",
|
|
221
|
-
});
|
|
222
|
-
await this.handleGitLabError(response);
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* List all issue links for a specific issue
|
|
226
|
-
*/
|
|
227
|
-
async listIssueLinks(projectId, issueIid) {
|
|
228
|
-
projectId = decodeURIComponent(projectId);
|
|
229
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/issues/${issueIid}/links`);
|
|
230
|
-
const response = await this.fetch(url.toString(), {});
|
|
231
|
-
await this.handleGitLabError(response);
|
|
232
|
-
const data = await response.json();
|
|
233
|
-
return z.array(GitLabIssueWithLinkDetailsSchema).parse(data);
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Get a specific issue link
|
|
237
|
-
*/
|
|
238
|
-
async getIssueLink(projectId, issueIid, issueLinkId) {
|
|
239
|
-
projectId = decodeURIComponent(projectId);
|
|
240
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/issues/${issueIid}/links/${issueLinkId}`);
|
|
241
|
-
const response = await this.fetch(url.toString(), {});
|
|
242
|
-
await this.handleGitLabError(response);
|
|
243
|
-
const data = await response.json();
|
|
244
|
-
return GitLabIssueLinkSchema.parse(data);
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Create an issue link between two issues
|
|
248
|
-
*/
|
|
249
|
-
async createIssueLink(projectId, issueIid, targetProjectId, targetIssueIid, linkType = "relates_to") {
|
|
250
|
-
projectId = decodeURIComponent(projectId);
|
|
251
|
-
targetProjectId = decodeURIComponent(targetProjectId);
|
|
252
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/issues/${issueIid}/links`);
|
|
253
|
-
const response = await this.fetch(url.toString(), {
|
|
254
|
-
method: "POST",
|
|
255
|
-
body: JSON.stringify({
|
|
256
|
-
target_project_id: targetProjectId,
|
|
257
|
-
target_issue_iid: targetIssueIid,
|
|
258
|
-
link_type: linkType,
|
|
259
|
-
}),
|
|
260
|
-
});
|
|
261
|
-
await this.handleGitLabError(response);
|
|
262
|
-
const data = await response.json();
|
|
263
|
-
return GitLabIssueLinkSchema.parse(data);
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Delete an issue link
|
|
267
|
-
*/
|
|
268
|
-
async deleteIssueLink(projectId, issueIid, issueLinkId) {
|
|
269
|
-
projectId = decodeURIComponent(projectId);
|
|
270
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/issues/${issueIid}/links/${issueLinkId}`);
|
|
271
|
-
const response = await this.fetch(url.toString(), {
|
|
272
|
-
method: "DELETE",
|
|
273
|
-
});
|
|
274
|
-
await this.handleGitLabError(response);
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Create a new merge request in a GitLab project
|
|
278
|
-
*/
|
|
279
|
-
async createMergeRequest(projectId, options) {
|
|
280
|
-
projectId = decodeURIComponent(projectId);
|
|
281
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests`);
|
|
282
|
-
const response = await this.fetch(url.toString(), {
|
|
283
|
-
method: "POST",
|
|
284
|
-
body: JSON.stringify({
|
|
285
|
-
title: options.title,
|
|
286
|
-
description: options.description,
|
|
287
|
-
source_branch: options.source_branch,
|
|
288
|
-
target_branch: options.target_branch,
|
|
289
|
-
assignee_ids: options.assignee_ids,
|
|
290
|
-
reviewer_ids: options.reviewer_ids,
|
|
291
|
-
labels: options.labels?.join(","),
|
|
292
|
-
allow_collaboration: options.allow_collaboration,
|
|
293
|
-
draft: options.draft,
|
|
294
|
-
remove_source_branch: options.remove_source_branch,
|
|
295
|
-
squash: options.squash,
|
|
296
|
-
}),
|
|
297
|
-
});
|
|
298
|
-
if (response.status === 400) {
|
|
299
|
-
const errorBody = await response.text();
|
|
300
|
-
throw new Error(`Invalid request: ${errorBody}`);
|
|
301
|
-
}
|
|
302
|
-
if (!response.ok) {
|
|
303
|
-
const errorBody = await response.text();
|
|
304
|
-
throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
|
|
305
|
-
}
|
|
306
|
-
const data = await response.json();
|
|
307
|
-
return GitLabMergeRequestSchema.parse(data);
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Shared helper function for listing discussions
|
|
311
|
-
*/
|
|
312
|
-
async listDiscussions(projectId, resourceType, resourceIid, options = {}) {
|
|
313
|
-
projectId = decodeURIComponent(projectId);
|
|
314
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/${resourceType}/${resourceIid}/discussions`);
|
|
315
|
-
if (options.page) {
|
|
316
|
-
url.searchParams.append("page", options.page.toString());
|
|
317
|
-
}
|
|
318
|
-
if (options.per_page) {
|
|
319
|
-
url.searchParams.append("per_page", options.per_page.toString());
|
|
320
|
-
}
|
|
321
|
-
const response = await this.fetch(url.toString(), {});
|
|
322
|
-
await this.handleGitLabError(response);
|
|
323
|
-
const discussions = await response.json();
|
|
324
|
-
const pagination = {
|
|
325
|
-
x_next_page: response.headers.get("x-next-page")
|
|
326
|
-
? parseInt(response.headers.get("x-next-page"))
|
|
327
|
-
: null,
|
|
328
|
-
x_page: response.headers.get("x-page") ? parseInt(response.headers.get("x-page")) : undefined,
|
|
329
|
-
x_per_page: response.headers.get("x-per-page")
|
|
330
|
-
? parseInt(response.headers.get("x-per-page"))
|
|
331
|
-
: undefined,
|
|
332
|
-
x_prev_page: response.headers.get("x-prev-page")
|
|
333
|
-
? parseInt(response.headers.get("x-prev-page"))
|
|
334
|
-
: null,
|
|
335
|
-
x_total: response.headers.get("x-total") ? parseInt(response.headers.get("x-total")) : null,
|
|
336
|
-
x_total_pages: response.headers.get("x-total-pages")
|
|
337
|
-
? parseInt(response.headers.get("x-total-pages"))
|
|
338
|
-
: null,
|
|
339
|
-
};
|
|
340
|
-
return PaginatedDiscussionsResponseSchema.parse({
|
|
341
|
-
items: discussions,
|
|
342
|
-
pagination: pagination,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* List merge request discussion items
|
|
347
|
-
*/
|
|
348
|
-
async listMergeRequestDiscussions(projectId, mergeRequestIid, options = {}) {
|
|
349
|
-
return this.listDiscussions(projectId, "merge_requests", mergeRequestIid, options);
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* List discussions for an issue
|
|
353
|
-
*/
|
|
354
|
-
async listIssueDiscussions(projectId, issueIid, options = {}) {
|
|
355
|
-
return this.listDiscussions(projectId, "issues", issueIid, options);
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* Modify an existing merge request thread note
|
|
359
|
-
*/
|
|
360
|
-
async updateMergeRequestNote(projectId, mergeRequestIid, discussionId, noteId, body, resolved) {
|
|
361
|
-
projectId = decodeURIComponent(projectId);
|
|
362
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/discussions/${discussionId}/notes/${noteId}`);
|
|
363
|
-
const payload = {};
|
|
364
|
-
if (body !== undefined) {
|
|
365
|
-
payload.body = body;
|
|
366
|
-
}
|
|
367
|
-
else if (resolved !== undefined) {
|
|
368
|
-
payload.resolved = resolved;
|
|
369
|
-
}
|
|
370
|
-
const response = await this.fetch(url.toString(), {
|
|
371
|
-
method: "PUT",
|
|
372
|
-
body: JSON.stringify(payload),
|
|
373
|
-
});
|
|
374
|
-
await this.handleGitLabError(response);
|
|
375
|
-
const data = await response.json();
|
|
376
|
-
return GitLabDiscussionNoteSchema.parse(data);
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* Update an issue discussion note
|
|
380
|
-
*/
|
|
381
|
-
async updateIssueNote(projectId, issueIid, discussionId, noteId, body) {
|
|
382
|
-
projectId = decodeURIComponent(projectId);
|
|
383
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/issues/${issueIid}/discussions/${discussionId}/notes/${noteId}`);
|
|
384
|
-
const payload = { body };
|
|
385
|
-
const response = await this.fetch(url.toString(), {
|
|
386
|
-
method: "PUT",
|
|
387
|
-
body: JSON.stringify(payload),
|
|
388
|
-
});
|
|
389
|
-
await this.handleGitLabError(response);
|
|
390
|
-
const data = await response.json();
|
|
391
|
-
return GitLabDiscussionNoteSchema.parse(data);
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* Create a note in an issue discussion
|
|
395
|
-
*/
|
|
396
|
-
async createIssueNote(projectId, issueIid, discussionId, body, createdAt) {
|
|
397
|
-
projectId = decodeURIComponent(projectId);
|
|
398
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/issues/${issueIid}/discussions/${discussionId}/notes`);
|
|
399
|
-
const payload = { body };
|
|
400
|
-
if (createdAt) {
|
|
401
|
-
payload.created_at = createdAt;
|
|
402
|
-
}
|
|
403
|
-
const response = await this.fetch(url.toString(), {
|
|
404
|
-
method: "POST",
|
|
405
|
-
body: JSON.stringify(payload),
|
|
406
|
-
});
|
|
407
|
-
await this.handleGitLabError(response);
|
|
408
|
-
const data = await response.json();
|
|
409
|
-
return GitLabDiscussionNoteSchema.parse(data);
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Add a new note to an existing merge request thread
|
|
413
|
-
*/
|
|
414
|
-
async createMergeRequestNote(projectId, mergeRequestIid, discussionId, body, createdAt) {
|
|
415
|
-
projectId = decodeURIComponent(projectId);
|
|
416
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/discussions/${discussionId}/notes`);
|
|
417
|
-
const payload = { body };
|
|
418
|
-
if (createdAt) {
|
|
419
|
-
payload.created_at = createdAt;
|
|
420
|
-
}
|
|
421
|
-
const response = await this.fetch(url.toString(), {
|
|
422
|
-
method: "POST",
|
|
423
|
-
body: JSON.stringify(payload),
|
|
424
|
-
});
|
|
425
|
-
await this.handleGitLabError(response);
|
|
426
|
-
const data = await response.json();
|
|
427
|
-
return GitLabDiscussionNoteSchema.parse(data);
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* Create or update a file in a GitLab project
|
|
431
|
-
*/
|
|
432
|
-
async createOrUpdateFile(projectId, filePath, content, commitMessage, branch, previousPath, last_commit_id, commit_id) {
|
|
433
|
-
projectId = decodeURIComponent(projectId);
|
|
434
|
-
const encodedPath = encodeURIComponent(filePath);
|
|
435
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/repository/files/${encodedPath}`);
|
|
436
|
-
const body = {
|
|
437
|
-
branch,
|
|
438
|
-
content,
|
|
439
|
-
commit_message: commitMessage,
|
|
440
|
-
encoding: "text",
|
|
441
|
-
...(previousPath ? { previous_path: previousPath } : {}),
|
|
442
|
-
};
|
|
443
|
-
let method = "POST";
|
|
444
|
-
try {
|
|
445
|
-
const fileData = await this.getFileContents(projectId, filePath, branch);
|
|
446
|
-
method = "PUT";
|
|
447
|
-
if (!Array.isArray(fileData)) {
|
|
448
|
-
if (!commit_id && fileData.commit_id) {
|
|
449
|
-
body.commit_id = fileData.commit_id;
|
|
450
|
-
}
|
|
451
|
-
else if (commit_id) {
|
|
452
|
-
body.commit_id = commit_id;
|
|
453
|
-
}
|
|
454
|
-
if (!last_commit_id && fileData.last_commit_id) {
|
|
455
|
-
body.last_commit_id = fileData.last_commit_id;
|
|
456
|
-
}
|
|
457
|
-
else if (last_commit_id) {
|
|
458
|
-
body.last_commit_id = last_commit_id;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
catch (error) {
|
|
463
|
-
if (!(error instanceof Error && error.message.includes("File not found"))) {
|
|
464
|
-
throw error;
|
|
465
|
-
}
|
|
466
|
-
if (commit_id) {
|
|
467
|
-
body.commit_id = commit_id;
|
|
468
|
-
}
|
|
469
|
-
if (last_commit_id) {
|
|
470
|
-
body.last_commit_id = last_commit_id;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
const response = await this.fetch(url.toString(), {
|
|
474
|
-
method,
|
|
475
|
-
body: JSON.stringify(body),
|
|
476
|
-
});
|
|
477
|
-
if (!response.ok) {
|
|
478
|
-
const errorBody = await response.text();
|
|
479
|
-
throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
|
|
480
|
-
}
|
|
481
|
-
const data = await response.json();
|
|
482
|
-
return GitLabCreateUpdateFileResponseSchema.parse(data);
|
|
483
|
-
}
|
|
484
|
-
/**
|
|
485
|
-
* Create a tree structure in a GitLab project repository
|
|
486
|
-
*/
|
|
487
|
-
async createTree(projectId, files, ref) {
|
|
488
|
-
projectId = decodeURIComponent(projectId);
|
|
489
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/repository/tree`);
|
|
490
|
-
if (ref) {
|
|
491
|
-
url.searchParams.append("ref", ref);
|
|
492
|
-
}
|
|
493
|
-
const response = await this.fetch(url.toString(), {
|
|
494
|
-
method: "POST",
|
|
495
|
-
body: JSON.stringify({
|
|
496
|
-
files: files.map(file => ({
|
|
497
|
-
file_path: file.path,
|
|
498
|
-
content: file.content,
|
|
499
|
-
encoding: "text",
|
|
500
|
-
})),
|
|
501
|
-
}),
|
|
502
|
-
});
|
|
503
|
-
if (response.status === 400) {
|
|
504
|
-
const errorBody = await response.text();
|
|
505
|
-
throw new Error(`Invalid request: ${errorBody}`);
|
|
506
|
-
}
|
|
507
|
-
if (!response.ok) {
|
|
508
|
-
const errorBody = await response.text();
|
|
509
|
-
throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
|
|
510
|
-
}
|
|
511
|
-
const data = await response.json();
|
|
512
|
-
return GitLabTreeSchema.parse(data);
|
|
513
|
-
}
|
|
514
|
-
/**
|
|
515
|
-
* Create a commit in a GitLab project repository
|
|
516
|
-
*/
|
|
517
|
-
async createCommit(projectId, message, branch, actions) {
|
|
518
|
-
projectId = decodeURIComponent(projectId);
|
|
519
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/repository/commits`);
|
|
520
|
-
const response = await this.fetch(url.toString(), {
|
|
521
|
-
method: "POST",
|
|
522
|
-
body: JSON.stringify({
|
|
523
|
-
branch,
|
|
524
|
-
commit_message: message,
|
|
525
|
-
actions: actions.map(action => ({
|
|
526
|
-
action: "create",
|
|
527
|
-
file_path: action.path,
|
|
528
|
-
content: action.content,
|
|
529
|
-
encoding: "text",
|
|
530
|
-
})),
|
|
531
|
-
}),
|
|
532
|
-
});
|
|
533
|
-
if (response.status === 400) {
|
|
534
|
-
const errorBody = await response.text();
|
|
535
|
-
throw new Error(`Invalid request: ${errorBody}`);
|
|
536
|
-
}
|
|
537
|
-
if (!response.ok) {
|
|
538
|
-
const errorBody = await response.text();
|
|
539
|
-
throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
|
|
540
|
-
}
|
|
541
|
-
const data = await response.json();
|
|
542
|
-
return GitLabCommitSchema.parse(data);
|
|
543
|
-
}
|
|
544
|
-
/**
|
|
545
|
-
* Search for GitLab projects
|
|
546
|
-
*/
|
|
547
|
-
async searchProjects(query, page = 1, perPage = 20) {
|
|
548
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects`);
|
|
549
|
-
url.searchParams.append("search", query);
|
|
550
|
-
url.searchParams.append("page", page.toString());
|
|
551
|
-
url.searchParams.append("per_page", perPage.toString());
|
|
552
|
-
url.searchParams.append("order_by", "id");
|
|
553
|
-
url.searchParams.append("sort", "desc");
|
|
554
|
-
const response = await this.fetch(url.toString(), {});
|
|
555
|
-
if (!response.ok) {
|
|
556
|
-
const errorBody = await response.text();
|
|
557
|
-
throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
|
|
558
|
-
}
|
|
559
|
-
const projects = (await response.json());
|
|
560
|
-
const totalCount = response.headers.get("x-total");
|
|
561
|
-
const totalPages = response.headers.get("x-total-pages");
|
|
562
|
-
const count = totalCount ? parseInt(totalCount) : projects.length;
|
|
563
|
-
return GitLabSearchResponseSchema.parse({
|
|
564
|
-
count,
|
|
565
|
-
total_pages: totalPages ? parseInt(totalPages) : Math.ceil(count / perPage),
|
|
566
|
-
current_page: page,
|
|
567
|
-
items: projects,
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Create a new GitLab repository
|
|
572
|
-
*/
|
|
573
|
-
async createRepository(options) {
|
|
574
|
-
const response = await this.fetch(`${config.GITLAB_API_URL}/projects`, {
|
|
575
|
-
method: "POST",
|
|
576
|
-
body: JSON.stringify({
|
|
577
|
-
name: options.name,
|
|
578
|
-
description: options.description,
|
|
579
|
-
visibility: options.visibility,
|
|
580
|
-
initialize_with_readme: options.initialize_with_readme,
|
|
581
|
-
default_branch: "main",
|
|
582
|
-
path: options.name.toLowerCase().replace(/\s+/g, "-"),
|
|
583
|
-
}),
|
|
584
|
-
});
|
|
585
|
-
if (!response.ok) {
|
|
586
|
-
const errorBody = await response.text();
|
|
587
|
-
throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
|
|
588
|
-
}
|
|
589
|
-
const data = await response.json();
|
|
590
|
-
return GitLabRepositorySchema.parse(data);
|
|
591
|
-
}
|
|
592
|
-
/**
|
|
593
|
-
* Get merge request details
|
|
594
|
-
*/
|
|
595
|
-
async getMergeRequest(projectId, mergeRequestIid, branchName) {
|
|
596
|
-
projectId = decodeURIComponent(projectId);
|
|
597
|
-
let url;
|
|
598
|
-
if (mergeRequestIid) {
|
|
599
|
-
url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}`);
|
|
600
|
-
}
|
|
601
|
-
else if (branchName) {
|
|
602
|
-
url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests?source_branch=${encodeURIComponent(branchName)}`);
|
|
603
|
-
}
|
|
604
|
-
else {
|
|
605
|
-
throw new Error("Either mergeRequestIid or branchName must be provided");
|
|
606
|
-
}
|
|
607
|
-
const response = await this.fetch(url.toString(), {});
|
|
608
|
-
await this.handleGitLabError(response);
|
|
609
|
-
const data = await response.json();
|
|
610
|
-
if (Array.isArray(data) && data.length > 0) {
|
|
611
|
-
return GitLabMergeRequestSchema.parse(data[0]);
|
|
612
|
-
}
|
|
613
|
-
return GitLabMergeRequestSchema.parse(data);
|
|
614
|
-
}
|
|
615
|
-
/**
|
|
616
|
-
* Get merge request changes/diffs
|
|
617
|
-
*/
|
|
618
|
-
async getMergeRequestDiffs(projectId, mergeRequestIid, branchName, view) {
|
|
619
|
-
projectId = decodeURIComponent(projectId);
|
|
620
|
-
if (!mergeRequestIid && !branchName) {
|
|
621
|
-
throw new Error("Either mergeRequestIid or branchName must be provided");
|
|
622
|
-
}
|
|
623
|
-
if (branchName && !mergeRequestIid) {
|
|
624
|
-
const mergeRequest = await this.getMergeRequest(projectId, undefined, branchName);
|
|
625
|
-
mergeRequestIid = mergeRequest.iid;
|
|
626
|
-
}
|
|
627
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/changes`);
|
|
628
|
-
if (view) {
|
|
629
|
-
url.searchParams.append("view", view);
|
|
630
|
-
}
|
|
631
|
-
const response = await this.fetch(url.toString(), {});
|
|
632
|
-
await this.handleGitLabError(response);
|
|
633
|
-
const data = (await response.json());
|
|
634
|
-
return z.array(GitLabDiffSchema).parse(data.changes);
|
|
635
|
-
}
|
|
636
|
-
/**
|
|
637
|
-
* Get merge request changes with detailed information
|
|
638
|
-
*/
|
|
639
|
-
async listMergeRequestDiffs(projectId, mergeRequestIid, branchName, page, perPage, unidiff) {
|
|
640
|
-
projectId = decodeURIComponent(projectId);
|
|
641
|
-
if (!mergeRequestIid && !branchName) {
|
|
642
|
-
throw new Error("Either mergeRequestIid or branchName must be provided");
|
|
643
|
-
}
|
|
644
|
-
if (branchName && !mergeRequestIid) {
|
|
645
|
-
const mergeRequest = await this.getMergeRequest(projectId, undefined, branchName);
|
|
646
|
-
mergeRequestIid = mergeRequest.iid;
|
|
647
|
-
}
|
|
648
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/diffs`);
|
|
649
|
-
if (page) {
|
|
650
|
-
url.searchParams.append("page", page.toString());
|
|
651
|
-
}
|
|
652
|
-
if (perPage) {
|
|
653
|
-
url.searchParams.append("per_page", perPage.toString());
|
|
654
|
-
}
|
|
655
|
-
if (unidiff) {
|
|
656
|
-
url.searchParams.append("unidiff", "true");
|
|
657
|
-
}
|
|
658
|
-
const response = await this.fetch(url.toString(), {});
|
|
659
|
-
await this.handleGitLabError(response);
|
|
660
|
-
return await response.json();
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Get branch comparison diffs
|
|
664
|
-
*/
|
|
665
|
-
async getBranchDiffs(projectId, from, to, straight) {
|
|
666
|
-
projectId = decodeURIComponent(projectId);
|
|
667
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/repository/compare`);
|
|
668
|
-
url.searchParams.append("from", from);
|
|
669
|
-
url.searchParams.append("to", to);
|
|
670
|
-
if (straight !== undefined) {
|
|
671
|
-
url.searchParams.append("straight", straight.toString());
|
|
672
|
-
}
|
|
673
|
-
const response = await this.fetch(url.toString(), {});
|
|
674
|
-
if (!response.ok) {
|
|
675
|
-
const errorBody = await response.text();
|
|
676
|
-
throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
|
|
677
|
-
}
|
|
678
|
-
const data = await response.json();
|
|
679
|
-
return GitLabCompareResultSchema.parse(data);
|
|
680
|
-
}
|
|
681
|
-
/**
|
|
682
|
-
* Update a merge request
|
|
683
|
-
*/
|
|
684
|
-
async updateMergeRequest(projectId, options, mergeRequestIid, branchName) {
|
|
685
|
-
projectId = decodeURIComponent(projectId);
|
|
686
|
-
if (!mergeRequestIid && !branchName) {
|
|
687
|
-
throw new Error("Either mergeRequestIid or branchName must be provided");
|
|
688
|
-
}
|
|
689
|
-
if (branchName && !mergeRequestIid) {
|
|
690
|
-
const mergeRequest = await this.getMergeRequest(projectId, undefined, branchName);
|
|
691
|
-
mergeRequestIid = mergeRequest.iid;
|
|
692
|
-
}
|
|
693
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}`);
|
|
694
|
-
const response = await this.fetch(url.toString(), {
|
|
695
|
-
method: "PUT",
|
|
696
|
-
body: JSON.stringify(options),
|
|
697
|
-
});
|
|
698
|
-
await this.handleGitLabError(response);
|
|
699
|
-
return GitLabMergeRequestSchema.parse(await response.json());
|
|
700
|
-
}
|
|
701
|
-
/**
|
|
702
|
-
* Create a new note (comment) on an issue or merge request
|
|
703
|
-
*/
|
|
704
|
-
async createNote(projectId, noteableType, noteableIid, body) {
|
|
705
|
-
projectId = decodeURIComponent(projectId);
|
|
706
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/${noteableType}s/${noteableIid}/notes`);
|
|
707
|
-
const response = await this.fetch(url.toString(), {
|
|
708
|
-
method: "POST",
|
|
709
|
-
body: JSON.stringify({ body }),
|
|
710
|
-
});
|
|
711
|
-
if (!response.ok) {
|
|
712
|
-
const errorText = await response.text();
|
|
713
|
-
throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorText}`);
|
|
714
|
-
}
|
|
715
|
-
return await response.json();
|
|
716
|
-
}
|
|
717
|
-
/**
|
|
718
|
-
* Create a new thread on a merge request
|
|
719
|
-
*/
|
|
720
|
-
async createMergeRequestThread(projectId, mergeRequestIid, body, position, createdAt) {
|
|
721
|
-
projectId = decodeURIComponent(projectId);
|
|
722
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/discussions`);
|
|
723
|
-
const payload = { body };
|
|
724
|
-
if (position) {
|
|
725
|
-
payload.position = position;
|
|
726
|
-
}
|
|
727
|
-
if (createdAt) {
|
|
728
|
-
payload.created_at = createdAt;
|
|
729
|
-
}
|
|
730
|
-
const response = await this.fetch(url.toString(), {
|
|
731
|
-
method: "POST",
|
|
732
|
-
body: JSON.stringify(payload),
|
|
733
|
-
});
|
|
734
|
-
await this.handleGitLabError(response);
|
|
735
|
-
const data = await response.json();
|
|
736
|
-
return GitLabDiscussionSchema.parse(data);
|
|
737
|
-
}
|
|
738
|
-
/**
|
|
739
|
-
* List all namespaces
|
|
740
|
-
*/
|
|
741
|
-
async listNamespaces(options) {
|
|
742
|
-
const url = new URL(`${config.GITLAB_API_URL}/namespaces`);
|
|
743
|
-
if (options.search) {
|
|
744
|
-
url.searchParams.append("search", options.search);
|
|
745
|
-
}
|
|
746
|
-
if (options.owned_only) {
|
|
747
|
-
url.searchParams.append("owned_only", "true");
|
|
748
|
-
}
|
|
749
|
-
if (options.top_level_only) {
|
|
750
|
-
url.searchParams.append("top_level_only", "true");
|
|
751
|
-
}
|
|
752
|
-
const response = await this.fetch(url.toString(), {});
|
|
753
|
-
await this.handleGitLabError(response);
|
|
754
|
-
const data = await response.json();
|
|
755
|
-
return z.array(GitLabNamespaceSchema).parse(data);
|
|
756
|
-
}
|
|
757
|
-
/**
|
|
758
|
-
* Get details on a namespace
|
|
759
|
-
*/
|
|
760
|
-
async getNamespace(id) {
|
|
761
|
-
const url = new URL(`${config.GITLAB_API_URL}/namespaces/${encodeURIComponent(id)}`);
|
|
762
|
-
const response = await this.fetch(url.toString(), {});
|
|
763
|
-
await this.handleGitLabError(response);
|
|
764
|
-
const data = await response.json();
|
|
765
|
-
return GitLabNamespaceSchema.parse(data);
|
|
766
|
-
}
|
|
767
|
-
/**
|
|
768
|
-
* Verify if a namespace exists
|
|
769
|
-
*/
|
|
770
|
-
async verifyNamespaceExistence(namespacePath, parentId) {
|
|
771
|
-
const url = new URL(`${config.GITLAB_API_URL}/namespaces/${encodeURIComponent(namespacePath)}/exists`);
|
|
772
|
-
if (parentId) {
|
|
773
|
-
url.searchParams.append("parent_id", parentId.toString());
|
|
774
|
-
}
|
|
775
|
-
const response = await this.fetch(url.toString(), {});
|
|
776
|
-
await this.handleGitLabError(response);
|
|
777
|
-
const data = await response.json();
|
|
778
|
-
return GitLabNamespaceExistsResponseSchema.parse(data);
|
|
779
|
-
}
|
|
780
|
-
/**
|
|
781
|
-
* Get a single project
|
|
782
|
-
*/
|
|
783
|
-
async getProject(projectId, options = {}) {
|
|
784
|
-
projectId = decodeURIComponent(projectId);
|
|
785
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}`);
|
|
786
|
-
if (options.license) {
|
|
787
|
-
url.searchParams.append("license", "true");
|
|
788
|
-
}
|
|
789
|
-
if (options.statistics) {
|
|
790
|
-
url.searchParams.append("statistics", "true");
|
|
791
|
-
}
|
|
792
|
-
if (options.with_custom_attributes) {
|
|
793
|
-
url.searchParams.append("with_custom_attributes", "true");
|
|
794
|
-
}
|
|
795
|
-
const response = await this.fetch(url.toString(), {});
|
|
796
|
-
await this.handleGitLabError(response);
|
|
797
|
-
const data = await response.json();
|
|
798
|
-
return GitLabRepositorySchema.parse(data);
|
|
799
|
-
}
|
|
800
|
-
/**
|
|
801
|
-
* List projects
|
|
802
|
-
*/
|
|
803
|
-
async listProjects(options = {}) {
|
|
804
|
-
const params = new URLSearchParams();
|
|
805
|
-
for (const [key, value] of Object.entries(options)) {
|
|
806
|
-
if (value !== undefined && value !== null) {
|
|
807
|
-
if (typeof value === "boolean") {
|
|
808
|
-
params.append(key, value ? "true" : "false");
|
|
809
|
-
}
|
|
810
|
-
else {
|
|
811
|
-
params.append(key, String(value));
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
const response = await this.fetch(`${config.GITLAB_API_URL}/projects?${params.toString()}`, {});
|
|
816
|
-
await this.handleGitLabError(response);
|
|
817
|
-
const data = await response.json();
|
|
818
|
-
return z.array(GitLabProjectSchema).parse(data);
|
|
819
|
-
}
|
|
820
|
-
/**
|
|
821
|
-
* List labels for a project
|
|
822
|
-
*/
|
|
823
|
-
async listLabels(projectId, options = {}) {
|
|
824
|
-
projectId = decodeURIComponent(projectId);
|
|
825
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/labels`);
|
|
826
|
-
Object.entries(options).forEach(([key, value]) => {
|
|
827
|
-
if (value !== undefined) {
|
|
828
|
-
if (typeof value === "boolean") {
|
|
829
|
-
url.searchParams.append(key, value ? "true" : "false");
|
|
830
|
-
}
|
|
831
|
-
else {
|
|
832
|
-
url.searchParams.append(key, String(value));
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
});
|
|
836
|
-
const response = await this.fetch(url.toString(), {});
|
|
837
|
-
await this.handleGitLabError(response);
|
|
838
|
-
const data = await response.json();
|
|
839
|
-
return data;
|
|
840
|
-
}
|
|
841
|
-
/**
|
|
842
|
-
* Get a single label from a project
|
|
843
|
-
*/
|
|
844
|
-
async getLabel(projectId, labelId, includeAncestorGroups) {
|
|
845
|
-
projectId = decodeURIComponent(projectId);
|
|
846
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/labels/${encodeURIComponent(String(labelId))}`);
|
|
847
|
-
if (includeAncestorGroups !== undefined) {
|
|
848
|
-
url.searchParams.append("include_ancestor_groups", includeAncestorGroups ? "true" : "false");
|
|
849
|
-
}
|
|
850
|
-
const response = await this.fetch(url.toString(), {});
|
|
851
|
-
await this.handleGitLabError(response);
|
|
852
|
-
const data = await response.json();
|
|
853
|
-
return data;
|
|
854
|
-
}
|
|
855
|
-
/**
|
|
856
|
-
* Create a new label in a project
|
|
857
|
-
*/
|
|
858
|
-
async createLabel(projectId, options) {
|
|
859
|
-
projectId = decodeURIComponent(projectId);
|
|
860
|
-
const response = await this.fetch(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/labels`, {
|
|
861
|
-
method: "POST",
|
|
862
|
-
body: JSON.stringify(options),
|
|
863
|
-
});
|
|
864
|
-
await this.handleGitLabError(response);
|
|
865
|
-
const data = await response.json();
|
|
866
|
-
return data;
|
|
867
|
-
}
|
|
868
|
-
/**
|
|
869
|
-
* Update an existing label in a project
|
|
870
|
-
*/
|
|
871
|
-
async updateLabel(projectId, labelId, options) {
|
|
872
|
-
projectId = decodeURIComponent(projectId);
|
|
873
|
-
const response = await this.fetch(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/labels/${encodeURIComponent(String(labelId))}`, {
|
|
874
|
-
method: "PUT",
|
|
875
|
-
body: JSON.stringify(options),
|
|
876
|
-
});
|
|
877
|
-
await this.handleGitLabError(response);
|
|
878
|
-
const data = await response.json();
|
|
879
|
-
return data;
|
|
880
|
-
}
|
|
881
|
-
/**
|
|
882
|
-
* Delete a label from a project
|
|
883
|
-
*/
|
|
884
|
-
async deleteLabel(projectId, labelId) {
|
|
885
|
-
projectId = decodeURIComponent(projectId);
|
|
886
|
-
const response = await this.fetch(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/labels/${encodeURIComponent(String(labelId))}`, {
|
|
887
|
-
method: "DELETE",
|
|
888
|
-
});
|
|
889
|
-
await this.handleGitLabError(response);
|
|
890
|
-
}
|
|
891
|
-
/**
|
|
892
|
-
* List all projects in a GitLab group
|
|
893
|
-
*/
|
|
894
|
-
async listGroupProjects(options) {
|
|
895
|
-
const url = new URL(`${config.GITLAB_API_URL}/groups/${encodeURIComponent(options.group_id)}/projects`);
|
|
896
|
-
if (options.include_subgroups)
|
|
897
|
-
url.searchParams.append("include_subgroups", "true");
|
|
898
|
-
if (options.search)
|
|
899
|
-
url.searchParams.append("search", options.search);
|
|
900
|
-
if (options.order_by)
|
|
901
|
-
url.searchParams.append("order_by", options.order_by);
|
|
902
|
-
if (options.sort)
|
|
903
|
-
url.searchParams.append("sort", options.sort);
|
|
904
|
-
if (options.page)
|
|
905
|
-
url.searchParams.append("page", options.page.toString());
|
|
906
|
-
if (options.per_page)
|
|
907
|
-
url.searchParams.append("per_page", options.per_page.toString());
|
|
908
|
-
if (options.archived !== undefined)
|
|
909
|
-
url.searchParams.append("archived", options.archived.toString());
|
|
910
|
-
if (options.visibility)
|
|
911
|
-
url.searchParams.append("visibility", options.visibility);
|
|
912
|
-
if (options.with_issues_enabled !== undefined)
|
|
913
|
-
url.searchParams.append("with_issues_enabled", options.with_issues_enabled.toString());
|
|
914
|
-
if (options.with_merge_requests_enabled !== undefined)
|
|
915
|
-
url.searchParams.append("with_merge_requests_enabled", options.with_merge_requests_enabled.toString());
|
|
916
|
-
if (options.min_access_level !== undefined)
|
|
917
|
-
url.searchParams.append("min_access_level", options.min_access_level.toString());
|
|
918
|
-
if (options.with_programming_language)
|
|
919
|
-
url.searchParams.append("with_programming_language", options.with_programming_language);
|
|
920
|
-
if (options.starred !== undefined)
|
|
921
|
-
url.searchParams.append("starred", options.starred.toString());
|
|
922
|
-
if (options.statistics !== undefined)
|
|
923
|
-
url.searchParams.append("statistics", options.statistics.toString());
|
|
924
|
-
if (options.with_custom_attributes !== undefined)
|
|
925
|
-
url.searchParams.append("with_custom_attributes", options.with_custom_attributes.toString());
|
|
926
|
-
if (options.with_security_reports !== undefined)
|
|
927
|
-
url.searchParams.append("with_security_reports", options.with_security_reports.toString());
|
|
928
|
-
const response = await this.fetch(url.toString(), {});
|
|
929
|
-
await this.handleGitLabError(response);
|
|
930
|
-
const projects = await response.json();
|
|
931
|
-
return GitLabProjectSchema.array().parse(projects);
|
|
932
|
-
}
|
|
933
|
-
// Wiki API helper methods
|
|
934
|
-
/**
|
|
935
|
-
* List wiki pages in a project
|
|
936
|
-
*/
|
|
937
|
-
async listWikiPages(projectId, options = {}) {
|
|
938
|
-
projectId = decodeURIComponent(projectId);
|
|
939
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/wikis`);
|
|
940
|
-
if (options.page)
|
|
941
|
-
url.searchParams.append("page", options.page.toString());
|
|
942
|
-
if (options.per_page)
|
|
943
|
-
url.searchParams.append("per_page", options.per_page.toString());
|
|
944
|
-
if (options.with_content)
|
|
945
|
-
url.searchParams.append("with_content", options.with_content.toString());
|
|
946
|
-
const response = await this.fetch(url.toString(), {});
|
|
947
|
-
await this.handleGitLabError(response);
|
|
948
|
-
const data = await response.json();
|
|
949
|
-
return GitLabWikiPageSchema.array().parse(data);
|
|
950
|
-
}
|
|
951
|
-
/**
|
|
952
|
-
* Get a specific wiki page
|
|
953
|
-
*/
|
|
954
|
-
async getWikiPage(projectId, slug) {
|
|
955
|
-
projectId = decodeURIComponent(projectId);
|
|
956
|
-
const response = await this.fetch(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/wikis/${encodeURIComponent(slug)}`, {});
|
|
957
|
-
await this.handleGitLabError(response);
|
|
958
|
-
const data = await response.json();
|
|
959
|
-
return GitLabWikiPageSchema.parse(data);
|
|
960
|
-
}
|
|
961
|
-
/**
|
|
962
|
-
* Create a new wiki page
|
|
963
|
-
*/
|
|
964
|
-
async createWikiPage(projectId, title, content, format) {
|
|
965
|
-
projectId = decodeURIComponent(projectId);
|
|
966
|
-
const body = { title, content };
|
|
967
|
-
if (format)
|
|
968
|
-
body.format = format;
|
|
969
|
-
const response = await this.fetch(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/wikis`, {
|
|
970
|
-
method: "POST",
|
|
971
|
-
body: JSON.stringify(body),
|
|
972
|
-
});
|
|
973
|
-
await this.handleGitLabError(response);
|
|
974
|
-
const data = await response.json();
|
|
975
|
-
return GitLabWikiPageSchema.parse(data);
|
|
976
|
-
}
|
|
977
|
-
/**
|
|
978
|
-
* Update an existing wiki page
|
|
979
|
-
*/
|
|
980
|
-
async updateWikiPage(projectId, slug, title, content, format) {
|
|
981
|
-
projectId = decodeURIComponent(projectId);
|
|
982
|
-
const body = {};
|
|
983
|
-
if (title)
|
|
984
|
-
body.title = title;
|
|
985
|
-
if (content)
|
|
986
|
-
body.content = content;
|
|
987
|
-
if (format)
|
|
988
|
-
body.format = format;
|
|
989
|
-
const response = await this.fetch(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/wikis/${encodeURIComponent(slug)}`, {
|
|
990
|
-
method: "PUT",
|
|
991
|
-
body: JSON.stringify(body),
|
|
992
|
-
});
|
|
993
|
-
await this.handleGitLabError(response);
|
|
994
|
-
const data = await response.json();
|
|
995
|
-
return GitLabWikiPageSchema.parse(data);
|
|
996
|
-
}
|
|
997
|
-
/**
|
|
998
|
-
* Delete a wiki page
|
|
999
|
-
*/
|
|
1000
|
-
async deleteWikiPage(projectId, slug) {
|
|
1001
|
-
projectId = decodeURIComponent(projectId);
|
|
1002
|
-
const response = await this.fetch(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/wikis/${encodeURIComponent(slug)}`, {
|
|
1003
|
-
method: "DELETE",
|
|
1004
|
-
});
|
|
1005
|
-
await this.handleGitLabError(response);
|
|
1006
|
-
}
|
|
1007
|
-
/**
|
|
1008
|
-
* List pipelines in a GitLab project
|
|
1009
|
-
*/
|
|
1010
|
-
async listPipelines(projectId, options = {}) {
|
|
1011
|
-
projectId = decodeURIComponent(projectId);
|
|
1012
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/pipelines`);
|
|
1013
|
-
Object.entries(options).forEach(([key, value]) => {
|
|
1014
|
-
if (value !== undefined) {
|
|
1015
|
-
url.searchParams.append(key, value.toString());
|
|
1016
|
-
}
|
|
1017
|
-
});
|
|
1018
|
-
const response = await this.fetch(url.toString(), {});
|
|
1019
|
-
await this.handleGitLabError(response);
|
|
1020
|
-
const data = await response.json();
|
|
1021
|
-
return z.array(GitLabPipelineSchema).parse(data);
|
|
1022
|
-
}
|
|
1023
|
-
/**
|
|
1024
|
-
* Get details of a specific pipeline
|
|
1025
|
-
*/
|
|
1026
|
-
async getPipeline(projectId, pipelineId) {
|
|
1027
|
-
projectId = decodeURIComponent(projectId);
|
|
1028
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/pipelines/${pipelineId}`);
|
|
1029
|
-
const response = await this.fetch(url.toString(), {});
|
|
1030
|
-
if (response.status === 404) {
|
|
1031
|
-
throw new Error(`Pipeline not found`);
|
|
1032
|
-
}
|
|
1033
|
-
await this.handleGitLabError(response);
|
|
1034
|
-
const data = await response.json();
|
|
1035
|
-
return GitLabPipelineSchema.parse(data);
|
|
1036
|
-
}
|
|
1037
|
-
/**
|
|
1038
|
-
* List all jobs in a specific pipeline
|
|
1039
|
-
*/
|
|
1040
|
-
async listPipelineJobs(projectId, pipelineId, options = {}) {
|
|
1041
|
-
projectId = decodeURIComponent(projectId);
|
|
1042
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/pipelines/${pipelineId}/jobs`);
|
|
1043
|
-
Object.entries(options).forEach(([key, value]) => {
|
|
1044
|
-
if (value !== undefined) {
|
|
1045
|
-
if (typeof value === "boolean") {
|
|
1046
|
-
url.searchParams.append(key, value ? "true" : "false");
|
|
1047
|
-
}
|
|
1048
|
-
else {
|
|
1049
|
-
url.searchParams.append(key, value.toString());
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
});
|
|
1053
|
-
const response = await this.fetch(url.toString(), {});
|
|
1054
|
-
if (response.status === 404) {
|
|
1055
|
-
throw new Error(`Pipeline not found`);
|
|
1056
|
-
}
|
|
1057
|
-
await this.handleGitLabError(response);
|
|
1058
|
-
const data = await response.json();
|
|
1059
|
-
return z.array(GitLabPipelineJobSchema).parse(data);
|
|
1060
|
-
}
|
|
1061
|
-
/**
|
|
1062
|
-
* Get a specific pipeline job
|
|
1063
|
-
*/
|
|
1064
|
-
async getPipelineJob(projectId, jobId) {
|
|
1065
|
-
projectId = decodeURIComponent(projectId);
|
|
1066
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/jobs/${jobId}`);
|
|
1067
|
-
const response = await this.fetch(url.toString(), {});
|
|
1068
|
-
if (response.status === 404) {
|
|
1069
|
-
throw new Error(`Job not found`);
|
|
1070
|
-
}
|
|
1071
|
-
await this.handleGitLabError(response);
|
|
1072
|
-
const data = await response.json();
|
|
1073
|
-
return GitLabPipelineJobSchema.parse(data);
|
|
1074
|
-
}
|
|
1075
|
-
/**
|
|
1076
|
-
* Get the output/trace of a pipeline job
|
|
1077
|
-
*/
|
|
1078
|
-
async getPipelineJobOutput(projectId, jobId, limit, offset) {
|
|
1079
|
-
projectId = decodeURIComponent(projectId);
|
|
1080
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/jobs/${jobId}/trace`);
|
|
1081
|
-
const response = await this.fetch(url.toString(), {
|
|
1082
|
-
headers: {
|
|
1083
|
-
Accept: "text/plain",
|
|
1084
|
-
},
|
|
1085
|
-
});
|
|
1086
|
-
if (response.status === 404) {
|
|
1087
|
-
throw new Error(`Job trace not found or job is not finished yet`);
|
|
1088
|
-
}
|
|
1089
|
-
await this.handleGitLabError(response);
|
|
1090
|
-
const fullTrace = await response.text();
|
|
1091
|
-
// Apply client-side pagination
|
|
1092
|
-
if (limit !== undefined || offset !== undefined) {
|
|
1093
|
-
const lines = fullTrace.split('\n');
|
|
1094
|
-
const startOffset = offset || 0;
|
|
1095
|
-
const maxLines = limit || 1000;
|
|
1096
|
-
const startIndex = Math.max(0, lines.length - startOffset - maxLines);
|
|
1097
|
-
const endIndex = lines.length - startOffset;
|
|
1098
|
-
const selectedLines = lines.slice(startIndex, endIndex);
|
|
1099
|
-
const result = selectedLines.join('\n');
|
|
1100
|
-
if (startIndex > 0 || endIndex < lines.length) {
|
|
1101
|
-
const totalLines = lines.length;
|
|
1102
|
-
const shownLines = selectedLines.length;
|
|
1103
|
-
const skippedFromStart = startIndex;
|
|
1104
|
-
const skippedFromEnd = startOffset;
|
|
1105
|
-
return `[Log truncated: showing ${shownLines} of ${totalLines} lines, skipped ${skippedFromStart} from start, ${skippedFromEnd} from end]\n\n${result}`;
|
|
1106
|
-
}
|
|
1107
|
-
return result;
|
|
1108
|
-
}
|
|
1109
|
-
return fullTrace;
|
|
1110
|
-
}
|
|
1111
|
-
/**
|
|
1112
|
-
* Create a new pipeline
|
|
1113
|
-
*/
|
|
1114
|
-
async createPipeline(projectId, ref, variables) {
|
|
1115
|
-
projectId = decodeURIComponent(projectId);
|
|
1116
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/pipeline`);
|
|
1117
|
-
const body = { ref };
|
|
1118
|
-
if (variables && variables.length > 0) {
|
|
1119
|
-
body.variables = variables;
|
|
1120
|
-
}
|
|
1121
|
-
const response = await this.fetch(url.toString(), {
|
|
1122
|
-
method: "POST",
|
|
1123
|
-
body: JSON.stringify(body),
|
|
1124
|
-
});
|
|
1125
|
-
await this.handleGitLabError(response);
|
|
1126
|
-
const data = await response.json();
|
|
1127
|
-
return GitLabPipelineSchema.parse(data);
|
|
1128
|
-
}
|
|
1129
|
-
/**
|
|
1130
|
-
* Retry a pipeline
|
|
1131
|
-
*/
|
|
1132
|
-
async retryPipeline(projectId, pipelineId) {
|
|
1133
|
-
projectId = decodeURIComponent(projectId);
|
|
1134
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/pipelines/${pipelineId}/retry`);
|
|
1135
|
-
const response = await this.fetch(url.toString(), {
|
|
1136
|
-
method: "POST",
|
|
1137
|
-
});
|
|
1138
|
-
await this.handleGitLabError(response);
|
|
1139
|
-
const data = await response.json();
|
|
1140
|
-
return GitLabPipelineSchema.parse(data);
|
|
1141
|
-
}
|
|
1142
|
-
/**
|
|
1143
|
-
* Cancel a pipeline
|
|
1144
|
-
*/
|
|
1145
|
-
async cancelPipeline(projectId, pipelineId) {
|
|
1146
|
-
projectId = decodeURIComponent(projectId);
|
|
1147
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/pipelines/${pipelineId}/cancel`);
|
|
1148
|
-
const response = await this.fetch(url.toString(), {
|
|
1149
|
-
method: "POST",
|
|
1150
|
-
});
|
|
1151
|
-
await this.handleGitLabError(response);
|
|
1152
|
-
const data = await response.json();
|
|
1153
|
-
return GitLabPipelineSchema.parse(data);
|
|
1154
|
-
}
|
|
1155
|
-
/**
|
|
1156
|
-
* Get the repository tree for a project
|
|
1157
|
-
*/
|
|
1158
|
-
async getRepositoryTree(options) {
|
|
1159
|
-
options.project_id = decodeURIComponent(options.project_id);
|
|
1160
|
-
const queryParams = new URLSearchParams();
|
|
1161
|
-
if (options.path)
|
|
1162
|
-
queryParams.append("path", options.path);
|
|
1163
|
-
if (options.ref)
|
|
1164
|
-
queryParams.append("ref", options.ref);
|
|
1165
|
-
if (options.recursive)
|
|
1166
|
-
queryParams.append("recursive", "true");
|
|
1167
|
-
if (options.per_page)
|
|
1168
|
-
queryParams.append("per_page", options.per_page.toString());
|
|
1169
|
-
if (options.page_token)
|
|
1170
|
-
queryParams.append("page_token", options.page_token);
|
|
1171
|
-
if (options.pagination)
|
|
1172
|
-
queryParams.append("pagination", options.pagination);
|
|
1173
|
-
const headers = {
|
|
1174
|
-
"Content-Type": "application/json",
|
|
1175
|
-
};
|
|
1176
|
-
if (config.IS_OLD) {
|
|
1177
|
-
headers["Private-Token"] = `${config.GITLAB_PERSONAL_ACCESS_TOKEN}`;
|
|
1178
|
-
}
|
|
1179
|
-
else {
|
|
1180
|
-
headers["Authorization"] = `Bearer ${config.GITLAB_PERSONAL_ACCESS_TOKEN}`;
|
|
1181
|
-
}
|
|
1182
|
-
const response = await this.fetch(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(options.project_id)}/repository/tree?${queryParams.toString()}`, {
|
|
1183
|
-
headers,
|
|
1184
|
-
});
|
|
1185
|
-
if (response.status === 404) {
|
|
1186
|
-
throw new Error("Repository or path not found");
|
|
1187
|
-
}
|
|
1188
|
-
if (!response.ok) {
|
|
1189
|
-
throw new Error(`Failed to get repository tree: ${response.statusText}`);
|
|
1190
|
-
}
|
|
1191
|
-
const data = await response.json();
|
|
1192
|
-
return z.array(GitLabTreeItemSchema).parse(data);
|
|
1193
|
-
}
|
|
1194
|
-
/**
|
|
1195
|
-
* List project milestones in a GitLab project
|
|
1196
|
-
*/
|
|
1197
|
-
async listProjectMilestones(projectId, options) {
|
|
1198
|
-
projectId = decodeURIComponent(projectId);
|
|
1199
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/milestones`);
|
|
1200
|
-
Object.entries(options).forEach(([key, value]) => {
|
|
1201
|
-
if (value !== undefined) {
|
|
1202
|
-
if (key === "iids" && Array.isArray(value) && value.length > 0) {
|
|
1203
|
-
value.forEach(iid => {
|
|
1204
|
-
url.searchParams.append("iids[]", iid.toString());
|
|
1205
|
-
});
|
|
1206
|
-
}
|
|
1207
|
-
else if (value !== undefined) {
|
|
1208
|
-
url.searchParams.append(key, value.toString());
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
});
|
|
1212
|
-
const response = await this.fetch(url.toString(), {});
|
|
1213
|
-
await this.handleGitLabError(response);
|
|
1214
|
-
const data = await response.json();
|
|
1215
|
-
return z.array(GitLabMilestonesSchema).parse(data);
|
|
1216
|
-
}
|
|
1217
|
-
/**
|
|
1218
|
-
* Get a single milestone in a GitLab project
|
|
1219
|
-
*/
|
|
1220
|
-
async getProjectMilestone(projectId, milestoneId) {
|
|
1221
|
-
projectId = decodeURIComponent(projectId);
|
|
1222
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/milestones/${milestoneId}`);
|
|
1223
|
-
const response = await this.fetch(url.toString(), {});
|
|
1224
|
-
await this.handleGitLabError(response);
|
|
1225
|
-
const data = await response.json();
|
|
1226
|
-
return GitLabMilestonesSchema.parse(data);
|
|
1227
|
-
}
|
|
1228
|
-
/**
|
|
1229
|
-
* Create a new milestone in a GitLab project
|
|
1230
|
-
*/
|
|
1231
|
-
async createProjectMilestone(projectId, options) {
|
|
1232
|
-
projectId = decodeURIComponent(projectId);
|
|
1233
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/milestones`);
|
|
1234
|
-
const response = await this.fetch(url.toString(), {
|
|
1235
|
-
method: "POST",
|
|
1236
|
-
body: JSON.stringify(options),
|
|
1237
|
-
});
|
|
1238
|
-
await this.handleGitLabError(response);
|
|
1239
|
-
const data = await response.json();
|
|
1240
|
-
return GitLabMilestonesSchema.parse(data);
|
|
1241
|
-
}
|
|
1242
|
-
/**
|
|
1243
|
-
* Edit an existing milestone in a GitLab project
|
|
1244
|
-
*/
|
|
1245
|
-
async editProjectMilestone(projectId, milestoneId, options) {
|
|
1246
|
-
projectId = decodeURIComponent(projectId);
|
|
1247
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/milestones/${milestoneId}`);
|
|
1248
|
-
const response = await this.fetch(url.toString(), {
|
|
1249
|
-
method: "PUT",
|
|
1250
|
-
body: JSON.stringify(options),
|
|
1251
|
-
});
|
|
1252
|
-
await this.handleGitLabError(response);
|
|
1253
|
-
const data = await response.json();
|
|
1254
|
-
return GitLabMilestonesSchema.parse(data);
|
|
1255
|
-
}
|
|
1256
|
-
/**
|
|
1257
|
-
* Delete a milestone from a GitLab project
|
|
1258
|
-
*/
|
|
1259
|
-
async deleteProjectMilestone(projectId, milestoneId) {
|
|
1260
|
-
projectId = decodeURIComponent(projectId);
|
|
1261
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/milestones/${milestoneId}`);
|
|
1262
|
-
const response = await this.fetch(url.toString(), {
|
|
1263
|
-
method: "DELETE",
|
|
1264
|
-
});
|
|
1265
|
-
await this.handleGitLabError(response);
|
|
1266
|
-
}
|
|
1267
|
-
/**
|
|
1268
|
-
* Get all issues assigned to a single milestone
|
|
1269
|
-
*/
|
|
1270
|
-
async getMilestoneIssues(projectId, milestoneId) {
|
|
1271
|
-
projectId = decodeURIComponent(projectId);
|
|
1272
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/milestones/${milestoneId}/issues`);
|
|
1273
|
-
const response = await this.fetch(url.toString(), {});
|
|
1274
|
-
await this.handleGitLabError(response);
|
|
1275
|
-
const data = await response.json();
|
|
1276
|
-
return z.array(GitLabIssueSchema).parse(data);
|
|
1277
|
-
}
|
|
1278
|
-
/**
|
|
1279
|
-
* Get all merge requests assigned to a single milestone
|
|
1280
|
-
*/
|
|
1281
|
-
async getMilestoneMergeRequests(projectId, milestoneId) {
|
|
1282
|
-
projectId = decodeURIComponent(projectId);
|
|
1283
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/milestones/${milestoneId}/merge_requests`);
|
|
1284
|
-
const response = await this.fetch(url.toString(), {});
|
|
1285
|
-
await this.handleGitLabError(response);
|
|
1286
|
-
const data = await response.json();
|
|
1287
|
-
return z.array(GitLabMergeRequestSchema).parse(data);
|
|
1288
|
-
}
|
|
1289
|
-
/**
|
|
1290
|
-
* Promote a project milestone to a group milestone
|
|
1291
|
-
*/
|
|
1292
|
-
async promoteProjectMilestone(projectId, milestoneId) {
|
|
1293
|
-
projectId = decodeURIComponent(projectId);
|
|
1294
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/milestones/${milestoneId}/promote`);
|
|
1295
|
-
const response = await this.fetch(url.toString(), {
|
|
1296
|
-
method: "POST",
|
|
1297
|
-
});
|
|
1298
|
-
await this.handleGitLabError(response);
|
|
1299
|
-
const data = await response.json();
|
|
1300
|
-
return GitLabMilestonesSchema.parse(data);
|
|
1301
|
-
}
|
|
1302
|
-
/**
|
|
1303
|
-
* Get all burndown chart events for a single milestone
|
|
1304
|
-
*/
|
|
1305
|
-
async getMilestoneBurndownEvents(projectId, milestoneId) {
|
|
1306
|
-
projectId = decodeURIComponent(projectId);
|
|
1307
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/milestones/${milestoneId}/burndown_events`);
|
|
1308
|
-
const response = await this.fetch(url.toString(), {});
|
|
1309
|
-
await this.handleGitLabError(response);
|
|
1310
|
-
const data = await response.json();
|
|
1311
|
-
return data;
|
|
1312
|
-
}
|
|
1313
|
-
/**
|
|
1314
|
-
* Get a single user from GitLab
|
|
1315
|
-
*/
|
|
1316
|
-
async getUser(username) {
|
|
1317
|
-
try {
|
|
1318
|
-
const url = new URL(`${config.GITLAB_API_URL}/users`);
|
|
1319
|
-
url.searchParams.append("username", username);
|
|
1320
|
-
const response = await this.fetch(url.toString(), {});
|
|
1321
|
-
await this.handleGitLabError(response);
|
|
1322
|
-
const users = await response.json();
|
|
1323
|
-
if (Array.isArray(users) && users.length > 0) {
|
|
1324
|
-
const exactMatch = users.find(user => user.username === username);
|
|
1325
|
-
if (exactMatch) {
|
|
1326
|
-
return GitLabUserSchema.parse(exactMatch);
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
return null;
|
|
1330
|
-
}
|
|
1331
|
-
catch (error) {
|
|
1332
|
-
console.error(`Error fetching user by username '${username}':`, error);
|
|
1333
|
-
return null;
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
/**
|
|
1337
|
-
* Get multiple users from GitLab
|
|
1338
|
-
*/
|
|
1339
|
-
async getUsers(usernames) {
|
|
1340
|
-
const users = {};
|
|
1341
|
-
for (const username of usernames) {
|
|
1342
|
-
try {
|
|
1343
|
-
const user = await this.getUser(username);
|
|
1344
|
-
users[username] = user;
|
|
1345
|
-
}
|
|
1346
|
-
catch (error) {
|
|
1347
|
-
console.error(`Error processing username '${username}':`, error);
|
|
1348
|
-
users[username] = null;
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
return GitLabUsersResponseSchema.parse(users);
|
|
1352
|
-
}
|
|
1353
|
-
/**
|
|
1354
|
-
* List repository commits
|
|
1355
|
-
*/
|
|
1356
|
-
async listCommits(projectId, options = {}) {
|
|
1357
|
-
projectId = decodeURIComponent(projectId);
|
|
1358
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/repository/commits`);
|
|
1359
|
-
if (options.ref_name)
|
|
1360
|
-
url.searchParams.append("ref_name", options.ref_name);
|
|
1361
|
-
if (options.since)
|
|
1362
|
-
url.searchParams.append("since", options.since);
|
|
1363
|
-
if (options.until)
|
|
1364
|
-
url.searchParams.append("until", options.until);
|
|
1365
|
-
if (options.path)
|
|
1366
|
-
url.searchParams.append("path", options.path);
|
|
1367
|
-
if (options.author)
|
|
1368
|
-
url.searchParams.append("author", options.author);
|
|
1369
|
-
if (options.all)
|
|
1370
|
-
url.searchParams.append("all", options.all.toString());
|
|
1371
|
-
if (options.with_stats)
|
|
1372
|
-
url.searchParams.append("with_stats", options.with_stats.toString());
|
|
1373
|
-
if (options.first_parent)
|
|
1374
|
-
url.searchParams.append("first_parent", options.first_parent.toString());
|
|
1375
|
-
if (options.order)
|
|
1376
|
-
url.searchParams.append("order", options.order);
|
|
1377
|
-
if (options.trailers)
|
|
1378
|
-
url.searchParams.append("trailers", options.trailers.toString());
|
|
1379
|
-
if (options.page)
|
|
1380
|
-
url.searchParams.append("page", options.page.toString());
|
|
1381
|
-
if (options.per_page)
|
|
1382
|
-
url.searchParams.append("per_page", options.per_page.toString());
|
|
1383
|
-
const response = await this.fetch(url.toString(), {});
|
|
1384
|
-
await this.handleGitLabError(response);
|
|
1385
|
-
const data = await response.json();
|
|
1386
|
-
return z.array(GitLabCommitSchema).parse(data);
|
|
1387
|
-
}
|
|
1388
|
-
/**
|
|
1389
|
-
* Get a single commit
|
|
1390
|
-
*/
|
|
1391
|
-
async getCommit(projectId, sha, stats) {
|
|
1392
|
-
projectId = decodeURIComponent(projectId);
|
|
1393
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/repository/commits/${encodeURIComponent(sha)}`);
|
|
1394
|
-
if (stats) {
|
|
1395
|
-
url.searchParams.append("stats", "true");
|
|
1396
|
-
}
|
|
1397
|
-
const response = await this.fetch(url.toString(), {});
|
|
1398
|
-
await this.handleGitLabError(response);
|
|
1399
|
-
const data = await response.json();
|
|
1400
|
-
return GitLabCommitSchema.parse(data);
|
|
1401
|
-
}
|
|
1402
|
-
/**
|
|
1403
|
-
* Get commit diff
|
|
1404
|
-
*/
|
|
1405
|
-
async getCommitDiff(projectId, sha) {
|
|
1406
|
-
projectId = decodeURIComponent(projectId);
|
|
1407
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/repository/commits/${encodeURIComponent(sha)}/diff`);
|
|
1408
|
-
const response = await this.fetch(url.toString(), {});
|
|
1409
|
-
await this.handleGitLabError(response);
|
|
1410
|
-
const data = await response.json();
|
|
1411
|
-
return z.array(GitLabDiffSchema).parse(data);
|
|
1412
|
-
}
|
|
1413
|
-
/**
|
|
1414
|
-
* Get details of the current authenticated User
|
|
1415
|
-
*/
|
|
1416
|
-
async getCurrentUser() {
|
|
1417
|
-
const url = new URL(`${config.GITLAB_API_URL}/user`);
|
|
1418
|
-
const response = await this.fetch(url.toString(), {});
|
|
1419
|
-
await this.handleGitLabError(response);
|
|
1420
|
-
const data = await response.json();
|
|
1421
|
-
return GitLabUserSchema.parse(data);
|
|
1422
|
-
}
|
|
1423
|
-
/**
|
|
1424
|
-
* Get a draft note
|
|
1425
|
-
*/
|
|
1426
|
-
async getDraftNote(projectId, mergeRequestIid, draftNoteId) {
|
|
1427
|
-
projectId = decodeURIComponent(projectId);
|
|
1428
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/draft_notes/${draftNoteId}`);
|
|
1429
|
-
const response = await this.fetch(url.toString(), {});
|
|
1430
|
-
await this.handleGitLabError(response);
|
|
1431
|
-
const data = await response.json();
|
|
1432
|
-
return GitLabDraftNoteSchema.parse(data);
|
|
1433
|
-
}
|
|
1434
|
-
/**
|
|
1435
|
-
* List draft notes
|
|
1436
|
-
*/
|
|
1437
|
-
async listDraftNotes(projectId, mergeRequestIid) {
|
|
1438
|
-
projectId = decodeURIComponent(projectId);
|
|
1439
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/draft_notes`);
|
|
1440
|
-
const response = await this.fetch(url.toString(), {});
|
|
1441
|
-
await this.handleGitLabError(response);
|
|
1442
|
-
const data = await response.json();
|
|
1443
|
-
return z.array(GitLabDraftNoteSchema).parse(data);
|
|
1444
|
-
}
|
|
1445
|
-
/**
|
|
1446
|
-
* Create a draft note
|
|
1447
|
-
*/
|
|
1448
|
-
async createDraftNote(projectId, mergeRequestIid, body, position, resolveDiscussion) {
|
|
1449
|
-
projectId = decodeURIComponent(projectId);
|
|
1450
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/draft_notes`);
|
|
1451
|
-
const requestBody = { note: body };
|
|
1452
|
-
if (position) {
|
|
1453
|
-
requestBody.position = position;
|
|
1454
|
-
}
|
|
1455
|
-
if (resolveDiscussion !== undefined) {
|
|
1456
|
-
requestBody.resolve_discussion = resolveDiscussion;
|
|
1457
|
-
}
|
|
1458
|
-
const response = await this.fetch(url.toString(), {
|
|
1459
|
-
method: "POST",
|
|
1460
|
-
body: JSON.stringify(requestBody),
|
|
1461
|
-
});
|
|
1462
|
-
await this.handleGitLabError(response);
|
|
1463
|
-
const data = await response.json();
|
|
1464
|
-
return GitLabDraftNoteSchema.parse(data);
|
|
1465
|
-
}
|
|
1466
|
-
/**
|
|
1467
|
-
* Update a draft note
|
|
1468
|
-
*/
|
|
1469
|
-
async updateDraftNote(projectId, mergeRequestIid, draftNoteId, body, position, resolveDiscussion) {
|
|
1470
|
-
projectId = decodeURIComponent(projectId);
|
|
1471
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/draft_notes/${draftNoteId}`);
|
|
1472
|
-
const requestBody = {};
|
|
1473
|
-
if (body !== undefined) {
|
|
1474
|
-
requestBody.note = body;
|
|
1475
|
-
}
|
|
1476
|
-
if (position) {
|
|
1477
|
-
requestBody.position = position;
|
|
1478
|
-
}
|
|
1479
|
-
if (resolveDiscussion !== undefined) {
|
|
1480
|
-
requestBody.resolve_discussion = resolveDiscussion;
|
|
1481
|
-
}
|
|
1482
|
-
const response = await this.fetch(url.toString(), {
|
|
1483
|
-
method: "PUT",
|
|
1484
|
-
body: JSON.stringify(requestBody),
|
|
1485
|
-
});
|
|
1486
|
-
await this.handleGitLabError(response);
|
|
1487
|
-
const data = await response.json();
|
|
1488
|
-
return GitLabDraftNoteSchema.parse(data);
|
|
1489
|
-
}
|
|
1490
|
-
/**
|
|
1491
|
-
* Delete a draft note
|
|
1492
|
-
*/
|
|
1493
|
-
async deleteDraftNote(projectId, mergeRequestIid, draftNoteId) {
|
|
1494
|
-
projectId = decodeURIComponent(projectId);
|
|
1495
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/draft_notes/${draftNoteId}`);
|
|
1496
|
-
const response = await this.fetch(url.toString(), {
|
|
1497
|
-
method: "DELETE",
|
|
1498
|
-
});
|
|
1499
|
-
await this.handleGitLabError(response);
|
|
1500
|
-
}
|
|
1501
|
-
/**
|
|
1502
|
-
* Publish a draft note
|
|
1503
|
-
*/
|
|
1504
|
-
async publishDraftNote(projectId, mergeRequestIid, draftNoteId) {
|
|
1505
|
-
projectId = decodeURIComponent(projectId);
|
|
1506
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/draft_notes/${draftNoteId}/publish`);
|
|
1507
|
-
const response = await this.fetch(url.toString(), {
|
|
1508
|
-
method: "PUT",
|
|
1509
|
-
});
|
|
1510
|
-
await this.handleGitLabError(response);
|
|
1511
|
-
// Handle empty response (204 No Content) or successful response
|
|
1512
|
-
const responseText = await response.text();
|
|
1513
|
-
if (!responseText || responseText.trim() === '') {
|
|
1514
|
-
// Return a success indicator for empty responses
|
|
1515
|
-
return {
|
|
1516
|
-
id: draftNoteId.toString(),
|
|
1517
|
-
body: "Draft note published successfully",
|
|
1518
|
-
author: { id: "unknown", username: "unknown" },
|
|
1519
|
-
created_at: new Date().toISOString(),
|
|
1520
|
-
};
|
|
1521
|
-
}
|
|
1522
|
-
const data = JSON.parse(responseText);
|
|
1523
|
-
return GitLabDiscussionNoteSchema.parse(data);
|
|
1524
|
-
}
|
|
1525
|
-
/**
|
|
1526
|
-
* Bulk publish draft notes
|
|
1527
|
-
*/
|
|
1528
|
-
async bulkPublishDraftNotes(projectId, mergeRequestIid) {
|
|
1529
|
-
projectId = decodeURIComponent(projectId);
|
|
1530
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/draft_notes/bulk_publish`);
|
|
1531
|
-
const response = await this.fetch(url.toString(), {
|
|
1532
|
-
method: "POST",
|
|
1533
|
-
body: JSON.stringify({}),
|
|
1534
|
-
});
|
|
1535
|
-
await this.handleGitLabError(response);
|
|
1536
|
-
// Handle empty response (204 No Content) or successful response
|
|
1537
|
-
const responseText = await response.text();
|
|
1538
|
-
if (!responseText || responseText.trim() === '') {
|
|
1539
|
-
// Return empty array for successful bulk publish with no content
|
|
1540
|
-
return [];
|
|
1541
|
-
}
|
|
1542
|
-
try {
|
|
1543
|
-
const data = JSON.parse(responseText);
|
|
1544
|
-
return z.array(GitLabDiscussionNoteSchema).parse(data);
|
|
1545
|
-
}
|
|
1546
|
-
catch (error) {
|
|
1547
|
-
return [];
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
/**
|
|
1551
|
-
* Merge a merge request
|
|
1552
|
-
*/
|
|
1553
|
-
async mergeMergeRequest(projectId, options, mergeRequestIid) {
|
|
1554
|
-
projectId = decodeURIComponent(projectId);
|
|
1555
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(this.getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/merge`);
|
|
1556
|
-
const response = await this.fetch(url.toString(), {
|
|
1557
|
-
method: "PUT",
|
|
1558
|
-
body: JSON.stringify(options),
|
|
1559
|
-
});
|
|
1560
|
-
await this.handleGitLabError(response);
|
|
1561
|
-
return GitLabMergeRequestSchema.parse(await response.json());
|
|
1562
|
-
}
|
|
1563
|
-
/**
|
|
1564
|
-
* Get issues assigned to the current user
|
|
1565
|
-
*/
|
|
1566
|
-
async myIssues(options = {}) {
|
|
1567
|
-
// Get current user to find their username
|
|
1568
|
-
const currentUser = await this.getCurrentUser();
|
|
1569
|
-
// Use getEffectiveProjectId to handle project ID resolution
|
|
1570
|
-
const effectiveProjectId = this.getEffectiveProjectId(options.project_id || "");
|
|
1571
|
-
// Use listIssues with assignee_username filter
|
|
1572
|
-
let listIssuesOptions = {
|
|
1573
|
-
state: options.state || "opened",
|
|
1574
|
-
labels: options.labels,
|
|
1575
|
-
milestone: options.milestone,
|
|
1576
|
-
search: options.search,
|
|
1577
|
-
created_after: options.created_after,
|
|
1578
|
-
created_before: options.created_before,
|
|
1579
|
-
updated_after: options.updated_after,
|
|
1580
|
-
updated_before: options.updated_before,
|
|
1581
|
-
per_page: options.per_page,
|
|
1582
|
-
page: options.page,
|
|
1583
|
-
};
|
|
1584
|
-
if (currentUser.username) {
|
|
1585
|
-
listIssuesOptions.assignee_username = [currentUser.username];
|
|
1586
|
-
}
|
|
1587
|
-
else {
|
|
1588
|
-
listIssuesOptions.assignee_id = currentUser.id;
|
|
1589
|
-
}
|
|
1590
|
-
return this.listIssues(effectiveProjectId, listIssuesOptions);
|
|
1591
|
-
}
|
|
1592
|
-
/**
|
|
1593
|
-
* List project members
|
|
1594
|
-
*/
|
|
1595
|
-
async listProjectMembers(projectId, options = {}) {
|
|
1596
|
-
projectId = decodeURIComponent(projectId);
|
|
1597
|
-
const effectiveProjectId = this.getEffectiveProjectId(projectId);
|
|
1598
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/members`);
|
|
1599
|
-
// Add query parameters
|
|
1600
|
-
if (options.query)
|
|
1601
|
-
url.searchParams.append("query", options.query);
|
|
1602
|
-
if (options.user_ids) {
|
|
1603
|
-
options.user_ids.forEach(id => url.searchParams.append("user_ids[]", id.toString()));
|
|
1604
|
-
}
|
|
1605
|
-
if (options.skip_users) {
|
|
1606
|
-
options.skip_users.forEach(id => url.searchParams.append("skip_users[]", id.toString()));
|
|
1607
|
-
}
|
|
1608
|
-
if (options.per_page)
|
|
1609
|
-
url.searchParams.append("per_page", options.per_page.toString());
|
|
1610
|
-
if (options.page)
|
|
1611
|
-
url.searchParams.append("page", options.page.toString());
|
|
1612
|
-
const response = await this.fetch(url.toString(), {});
|
|
1613
|
-
await this.handleGitLabError(response);
|
|
1614
|
-
const data = await response.json();
|
|
1615
|
-
return z.array(GitLabProjectMemberSchema).parse(data);
|
|
1616
|
-
}
|
|
1617
|
-
/**
|
|
1618
|
-
* Upload a file for markdown usage
|
|
1619
|
-
*/
|
|
1620
|
-
async markdownUpload(projectId, filePath) {
|
|
1621
|
-
projectId = decodeURIComponent(projectId);
|
|
1622
|
-
const effectiveProjectId = this.getEffectiveProjectId(projectId);
|
|
1623
|
-
// Check if file exists
|
|
1624
|
-
if (!fs.existsSync(filePath)) {
|
|
1625
|
-
throw new Error(`File not found: ${filePath}`);
|
|
1626
|
-
}
|
|
1627
|
-
// Read the file
|
|
1628
|
-
const fileBuffer = fs.readFileSync(filePath);
|
|
1629
|
-
const fileName = path.basename(filePath);
|
|
1630
|
-
// Create form data
|
|
1631
|
-
const FormData = (await import("form-data")).default;
|
|
1632
|
-
const form = new FormData();
|
|
1633
|
-
form.append("file", fileBuffer, {
|
|
1634
|
-
filename: fileName,
|
|
1635
|
-
contentType: "application/octet-stream",
|
|
1636
|
-
});
|
|
1637
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/uploads`);
|
|
1638
|
-
// Need to handle form data specially
|
|
1639
|
-
const headers = form.getHeaders();
|
|
1640
|
-
const response = await this.fetch(url.toString(), {
|
|
1641
|
-
method: "POST",
|
|
1642
|
-
body: form,
|
|
1643
|
-
headers: headers,
|
|
1644
|
-
});
|
|
1645
|
-
await this.handleGitLabError(response);
|
|
1646
|
-
const data = await response.json();
|
|
1647
|
-
return GitLabMarkdownUploadSchema.parse(data);
|
|
1648
|
-
}
|
|
1649
|
-
/**
|
|
1650
|
-
* Download an attachment from a GitLab project
|
|
1651
|
-
*/
|
|
1652
|
-
async downloadAttachment(projectId, secret, filename, localPath) {
|
|
1653
|
-
projectId = decodeURIComponent(projectId);
|
|
1654
|
-
const effectiveProjectId = this.getEffectiveProjectId(projectId);
|
|
1655
|
-
const url = new URL(`${config.GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/uploads/${secret}/${filename}`);
|
|
1656
|
-
const response = await this.fetch(url.toString(), {});
|
|
1657
|
-
await this.handleGitLabError(response);
|
|
1658
|
-
// Get the file content as buffer
|
|
1659
|
-
const buffer = await response.arrayBuffer();
|
|
1660
|
-
// Determine the save path
|
|
1661
|
-
const savePath = localPath ? path.join(localPath, filename) : filename;
|
|
1662
|
-
// Write the file to disk
|
|
1663
|
-
fs.writeFileSync(savePath, Buffer.from(buffer));
|
|
1664
|
-
return savePath;
|
|
1665
|
-
}
|
|
1666
|
-
}
|