gitlab-mcp 1.1.0 → 1.2.0

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.
Files changed (106) hide show
  1. package/LICENSE +21 -0
  2. package/dist/config/env.d.ts +56 -0
  3. package/dist/config/env.js +163 -0
  4. package/dist/config/env.js.map +1 -0
  5. package/dist/http-app.d.ts +45 -0
  6. package/dist/http-app.js +550 -0
  7. package/dist/http-app.js.map +1 -0
  8. package/dist/http.d.ts +2 -0
  9. package/dist/http.js +65 -0
  10. package/dist/http.js.map +1 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +65 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/lib/auth-context.d.ts +9 -0
  15. package/dist/lib/auth-context.js +9 -0
  16. package/dist/lib/auth-context.js.map +1 -0
  17. package/dist/lib/gitlab-client.d.ts +331 -0
  18. package/dist/lib/gitlab-client.js +1025 -0
  19. package/dist/lib/gitlab-client.js.map +1 -0
  20. package/dist/lib/logger.d.ts +2 -0
  21. package/dist/lib/logger.js +13 -0
  22. package/dist/lib/logger.js.map +1 -0
  23. package/dist/lib/network.d.ts +3 -0
  24. package/dist/lib/network.js +38 -0
  25. package/dist/lib/network.js.map +1 -0
  26. package/dist/lib/oauth.d.ts +29 -0
  27. package/dist/lib/oauth.js +220 -0
  28. package/dist/lib/oauth.js.map +1 -0
  29. package/dist/lib/output.d.ts +14 -0
  30. package/dist/lib/output.js +38 -0
  31. package/dist/lib/output.js.map +1 -0
  32. package/dist/lib/policy.d.ts +25 -0
  33. package/dist/lib/policy.js +48 -0
  34. package/dist/lib/policy.js.map +1 -0
  35. package/dist/lib/request-runtime.d.ts +26 -0
  36. package/dist/lib/request-runtime.js +323 -0
  37. package/dist/lib/request-runtime.js.map +1 -0
  38. package/dist/lib/sanitize.d.ts +1 -0
  39. package/dist/lib/sanitize.js +21 -0
  40. package/dist/lib/sanitize.js.map +1 -0
  41. package/dist/lib/session-capacity.d.ts +8 -0
  42. package/dist/lib/session-capacity.js +7 -0
  43. package/dist/lib/session-capacity.js.map +1 -0
  44. package/dist/server/build-server.d.ts +3 -0
  45. package/dist/server/build-server.js +13 -0
  46. package/dist/server/build-server.js.map +1 -0
  47. package/dist/tools/gitlab.d.ts +9 -0
  48. package/dist/tools/gitlab.js +2576 -0
  49. package/dist/tools/gitlab.js.map +1 -0
  50. package/dist/tools/health.d.ts +2 -0
  51. package/dist/tools/health.js +21 -0
  52. package/dist/tools/health.js.map +1 -0
  53. package/dist/tools/mr-code-context.d.ts +38 -0
  54. package/dist/tools/mr-code-context.js +330 -0
  55. package/dist/tools/mr-code-context.js.map +1 -0
  56. package/{src/types/context.ts → dist/types/context.d.ts} +5 -6
  57. package/dist/types/context.js +2 -0
  58. package/dist/types/context.js.map +1 -0
  59. package/docs/configuration.md +6 -6
  60. package/docs/mcp-integration-testing-best-practices.md +981 -0
  61. package/package.json +13 -1
  62. package/.dockerignore +0 -7
  63. package/.editorconfig +0 -9
  64. package/.env.example +0 -75
  65. package/.github/workflows/nodejs.yml +0 -31
  66. package/.github/workflows/npm-publish.yml +0 -31
  67. package/.husky/pre-commit +0 -1
  68. package/.nvmrc +0 -1
  69. package/.prettierrc.json +0 -6
  70. package/Dockerfile +0 -20
  71. package/docker-compose.yml +0 -10
  72. package/eslint.config.js +0 -23
  73. package/scripts/get-oauth-token.example.sh +0 -15
  74. package/src/config/env.ts +0 -171
  75. package/src/http.ts +0 -620
  76. package/src/index.ts +0 -77
  77. package/src/lib/auth-context.ts +0 -19
  78. package/src/lib/gitlab-client.ts +0 -1810
  79. package/src/lib/logger.ts +0 -17
  80. package/src/lib/network.ts +0 -45
  81. package/src/lib/oauth.ts +0 -287
  82. package/src/lib/output.ts +0 -51
  83. package/src/lib/policy.ts +0 -78
  84. package/src/lib/request-runtime.ts +0 -376
  85. package/src/lib/sanitize.ts +0 -25
  86. package/src/lib/session-capacity.ts +0 -14
  87. package/src/server/build-server.ts +0 -17
  88. package/src/tools/gitlab.ts +0 -3135
  89. package/src/tools/health.ts +0 -27
  90. package/src/tools/mr-code-context.ts +0 -473
  91. package/tests/auth-context.test.ts +0 -102
  92. package/tests/gitlab-client.test.ts +0 -672
  93. package/tests/graphql-guard.test.ts +0 -121
  94. package/tests/integration/agent-loop.integration.test.ts +0 -558
  95. package/tests/integration/server.integration.test.ts +0 -543
  96. package/tests/mr-code-context.test.ts +0 -600
  97. package/tests/oauth.test.ts +0 -43
  98. package/tests/output.test.ts +0 -186
  99. package/tests/policy.test.ts +0 -324
  100. package/tests/request-runtime.test.ts +0 -252
  101. package/tests/sanitize.test.ts +0 -123
  102. package/tests/session-capacity.test.ts +0 -49
  103. package/tests/upload-reference.test.ts +0 -88
  104. package/tsconfig.build.json +0 -11
  105. package/tsconfig.json +0 -21
  106. package/vitest.config.ts +0 -12
@@ -1,1810 +0,0 @@
1
- import * as fs from "node:fs/promises";
2
- import * as path from "node:path";
3
-
4
- import { getSessionAuth, type SessionAuth } from "./auth-context.js";
5
-
6
- export interface GitLabClientOptions {
7
- timeoutMs?: number;
8
- apiUrls?: string[];
9
- beforeRequest?: (
10
- context: GitLabBeforeRequestContext
11
- ) => Promise<GitLabBeforeRequestResult | void>;
12
- }
13
-
14
- export interface GitLabRequestOptions {
15
- query?: Record<string, string | number | boolean | undefined | null>;
16
- body?: BodyInit;
17
- headers?: HeadersInit;
18
- token?: string;
19
- apiUrl?: string;
20
- }
21
-
22
- export interface GitLabBeforeRequestContext {
23
- url: URL;
24
- method: string;
25
- headers: Headers;
26
- body?: BodyInit;
27
- token?: string;
28
- }
29
-
30
- export interface GitLabBeforeRequestResult {
31
- headers?: Headers;
32
- body?: BodyInit;
33
- token?: string;
34
- fetchImpl?: typeof fetch;
35
- }
36
-
37
- export interface GitLabProject {
38
- id: number;
39
- name: string;
40
- description: string | null;
41
- path_with_namespace: string;
42
- default_branch: string | null;
43
- web_url: string;
44
- visibility: string;
45
- last_activity_at: string;
46
- }
47
-
48
- export interface PushFileAction {
49
- action: "create" | "delete" | "move" | "update" | "chmod";
50
- file_path: string;
51
- previous_path?: string;
52
- content?: string;
53
- encoding?: "text" | "base64";
54
- execute_filemode?: boolean;
55
- last_commit_id?: string;
56
- }
57
-
58
- export interface MergeRequestCodeContextFile {
59
- old_path: string;
60
- new_path: string;
61
- new_file: boolean;
62
- renamed_file: boolean;
63
- deleted_file: boolean;
64
- diff: string;
65
- }
66
-
67
- export class GitLabApiError extends Error {
68
- constructor(
69
- message: string,
70
- public readonly status: number,
71
- public readonly details?: unknown
72
- ) {
73
- super(message);
74
- this.name = "GitLabApiError";
75
- }
76
- }
77
-
78
- export class GitLabClient {
79
- private readonly baseApiUrl: string;
80
- private readonly apiUrls: string[];
81
- private nextApiUrlIndex = 0;
82
- private readonly defaultToken?: string;
83
- private readonly timeoutMs: number;
84
- private readonly beforeRequest?: GitLabClientOptions["beforeRequest"];
85
-
86
- constructor(baseApiUrl: string, defaultToken?: string, options: GitLabClientOptions = {}) {
87
- this.baseApiUrl = normalizeApiUrl(baseApiUrl);
88
- const configuredApiUrls = options.apiUrls
89
- ?.map((item) => normalizeApiUrl(item))
90
- .filter((item) => item.length > 0) ?? [this.baseApiUrl];
91
- this.apiUrls = configuredApiUrls.length > 0 ? configuredApiUrls : [this.baseApiUrl];
92
- this.defaultToken = defaultToken;
93
- this.timeoutMs = options.timeoutMs ?? 20_000;
94
- this.beforeRequest = options.beforeRequest;
95
- }
96
-
97
- // projects
98
- getProject(projectId: string, options?: GitLabRequestOptions): Promise<unknown> {
99
- return this.get(`/projects/${encode(projectId)}`, options);
100
- }
101
-
102
- listProjects(options: GitLabRequestOptions = {}): Promise<unknown> {
103
- return this.get("/projects", options);
104
- }
105
-
106
- createRepository(
107
- payload: {
108
- name: string;
109
- description?: string;
110
- visibility?: "private" | "internal" | "public";
111
- initialize_with_readme?: boolean;
112
- path?: string;
113
- namespace_id?: string | number;
114
- default_branch?: string;
115
- },
116
- options: GitLabRequestOptions = {}
117
- ): Promise<unknown> {
118
- return this.post("/projects", {
119
- ...options,
120
- body: JSON.stringify(payload),
121
- headers: {
122
- "Content-Type": "application/json",
123
- ...(options.headers ?? {})
124
- }
125
- });
126
- }
127
-
128
- listProjectMembers(projectId: string, options: GitLabRequestOptions = {}): Promise<unknown> {
129
- return this.get(`/projects/${encode(projectId)}/members/all`, options);
130
- }
131
-
132
- listGroupProjects(groupId: string, options: GitLabRequestOptions = {}): Promise<unknown> {
133
- return this.get(`/groups/${encode(groupId)}/projects`, options);
134
- }
135
-
136
- forkRepository(
137
- projectId: string,
138
- payload: {
139
- namespace?: string;
140
- namespace_id?: string | number;
141
- path?: string;
142
- name?: string;
143
- description?: string;
144
- visibility?: "private" | "internal" | "public";
145
- default_branch?: string;
146
- } = {},
147
- options: GitLabRequestOptions = {}
148
- ): Promise<unknown> {
149
- return this.post(`/projects/${encode(projectId)}/fork`, {
150
- ...options,
151
- body: JSON.stringify(payload),
152
- headers: {
153
- "Content-Type": "application/json",
154
- ...(options.headers ?? {})
155
- }
156
- });
157
- }
158
-
159
- searchProjects(search: string, limit = 10, options: GitLabRequestOptions = {}): Promise<unknown> {
160
- return this.get("/projects", {
161
- ...options,
162
- query: {
163
- search,
164
- simple: true,
165
- per_page: limit,
166
- ...(options.query ?? {})
167
- }
168
- });
169
- }
170
-
171
- searchRepositories(search: string, options: GitLabRequestOptions = {}): Promise<unknown> {
172
- return this.get("/search", {
173
- ...options,
174
- query: {
175
- scope: "projects",
176
- search,
177
- ...(options.query ?? {})
178
- }
179
- });
180
- }
181
-
182
- searchCodeBlobs(
183
- projectId: string,
184
- search: string,
185
- options: GitLabRequestOptions = {}
186
- ): Promise<unknown> {
187
- return this.get(`/projects/${encode(projectId)}/search`, {
188
- ...options,
189
- query: {
190
- scope: "blobs",
191
- search,
192
- ...(options.query ?? {})
193
- }
194
- });
195
- }
196
-
197
- // repository/files
198
- getRepositoryTree(projectId: string, options: GitLabRequestOptions = {}): Promise<unknown> {
199
- return this.get(`/projects/${encode(projectId)}/repository/tree`, options);
200
- }
201
-
202
- getFileContents(
203
- projectId: string,
204
- filePath: string,
205
- ref: string,
206
- options: GitLabRequestOptions = {}
207
- ): Promise<unknown> {
208
- return this.get(`/projects/${encode(projectId)}/repository/files/${encode(filePath)}`, {
209
- ...options,
210
- query: {
211
- ref,
212
- ...(options.query ?? {})
213
- }
214
- });
215
- }
216
-
217
- createOrUpdateFile(
218
- projectId: string,
219
- filePath: string,
220
- payload: {
221
- branch: string;
222
- content: string;
223
- commit_message: string;
224
- author_email?: string;
225
- author_name?: string;
226
- encoding?: "text" | "base64";
227
- execute_filemode?: boolean;
228
- start_branch?: string;
229
- last_commit_id?: string;
230
- },
231
- options: GitLabRequestOptions = {}
232
- ): Promise<unknown> {
233
- return this.put(`/projects/${encode(projectId)}/repository/files/${encode(filePath)}`, {
234
- ...options,
235
- body: JSON.stringify(payload),
236
- headers: {
237
- "Content-Type": "application/json",
238
- ...(options.headers ?? {})
239
- }
240
- });
241
- }
242
-
243
- pushFiles(
244
- projectId: string,
245
- payload: {
246
- branch: string;
247
- commit_message: string;
248
- actions: PushFileAction[];
249
- start_branch?: string;
250
- author_name?: string;
251
- author_email?: string;
252
- force?: boolean;
253
- },
254
- options: GitLabRequestOptions = {}
255
- ): Promise<unknown> {
256
- return this.post(`/projects/${encode(projectId)}/repository/commits`, {
257
- ...options,
258
- body: JSON.stringify(payload),
259
- headers: {
260
- "Content-Type": "application/json",
261
- ...(options.headers ?? {})
262
- }
263
- });
264
- }
265
-
266
- createBranch(
267
- projectId: string,
268
- payload: {
269
- branch: string;
270
- ref: string;
271
- },
272
- options: GitLabRequestOptions = {}
273
- ): Promise<unknown> {
274
- return this.post(`/projects/${encode(projectId)}/repository/branches`, {
275
- ...options,
276
- query: payload
277
- });
278
- }
279
-
280
- getBranchDiffs(
281
- projectId: string,
282
- payload: {
283
- from: string;
284
- to: string;
285
- straight?: boolean;
286
- },
287
- options: GitLabRequestOptions = {}
288
- ): Promise<unknown> {
289
- return this.get(`/projects/${encode(projectId)}/repository/compare`, {
290
- ...options,
291
- query: payload
292
- });
293
- }
294
-
295
- listCommits(projectId: string, options: GitLabRequestOptions = {}): Promise<unknown> {
296
- return this.get(`/projects/${encode(projectId)}/repository/commits`, options);
297
- }
298
-
299
- getCommit(projectId: string, sha: string, options: GitLabRequestOptions = {}): Promise<unknown> {
300
- return this.get(`/projects/${encode(projectId)}/repository/commits/${encode(sha)}`, options);
301
- }
302
-
303
- getCommitDiff(
304
- projectId: string,
305
- sha: string,
306
- options: GitLabRequestOptions = {}
307
- ): Promise<unknown> {
308
- return this.get(
309
- `/projects/${encode(projectId)}/repository/commits/${encode(sha)}/diff`,
310
- options
311
- );
312
- }
313
-
314
- // merge requests
315
- listMergeRequests(projectId: string, options: GitLabRequestOptions = {}): Promise<unknown> {
316
- return this.get(`/projects/${encode(projectId)}/merge_requests`, options);
317
- }
318
-
319
- listGlobalMergeRequests(options: GitLabRequestOptions = {}): Promise<unknown> {
320
- return this.get("/merge_requests", options);
321
- }
322
-
323
- getMergeRequest(
324
- projectId: string,
325
- mergeRequestIid: string,
326
- options: GitLabRequestOptions = {}
327
- ): Promise<unknown> {
328
- return this.get(
329
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}`,
330
- options
331
- );
332
- }
333
-
334
- createMergeRequest(
335
- projectId: string,
336
- payload: {
337
- source_branch: string;
338
- target_branch: string;
339
- title: string;
340
- description?: string;
341
- target_project_id?: string | number;
342
- assignee_ids?: number[];
343
- reviewer_ids?: number[];
344
- labels?: string;
345
- allow_collaboration?: boolean;
346
- remove_source_branch?: boolean;
347
- squash?: boolean;
348
- draft?: boolean;
349
- },
350
- options: GitLabRequestOptions = {}
351
- ): Promise<unknown> {
352
- return this.post(`/projects/${encode(projectId)}/merge_requests`, {
353
- ...options,
354
- body: JSON.stringify(payload),
355
- headers: {
356
- "Content-Type": "application/json",
357
- ...(options.headers ?? {})
358
- }
359
- });
360
- }
361
-
362
- updateMergeRequest(
363
- projectId: string,
364
- mergeRequestIid: string,
365
- payload: Record<string, unknown>,
366
- options: GitLabRequestOptions = {}
367
- ): Promise<unknown> {
368
- return this.put(`/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}`, {
369
- ...options,
370
- body: JSON.stringify(payload),
371
- headers: {
372
- "Content-Type": "application/json",
373
- ...(options.headers ?? {})
374
- }
375
- });
376
- }
377
-
378
- mergeMergeRequest(
379
- projectId: string,
380
- mergeRequestIid: string,
381
- payload: Record<string, unknown> = {},
382
- options: GitLabRequestOptions = {}
383
- ): Promise<unknown> {
384
- return this.put(
385
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/merge`,
386
- {
387
- ...options,
388
- body: JSON.stringify(payload),
389
- headers: {
390
- "Content-Type": "application/json",
391
- ...(options.headers ?? {})
392
- }
393
- }
394
- );
395
- }
396
-
397
- getMergeRequestDiffs(
398
- projectId: string,
399
- mergeRequestIid: string,
400
- options: GitLabRequestOptions = {}
401
- ): Promise<unknown> {
402
- return this.get(
403
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/changes`,
404
- options
405
- );
406
- }
407
-
408
- listMergeRequestDiffs(
409
- projectId: string,
410
- mergeRequestIid: string,
411
- options: GitLabRequestOptions = {}
412
- ): Promise<unknown> {
413
- return this.get(
414
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/diffs`,
415
- options
416
- );
417
- }
418
-
419
- listMergeRequestVersions(
420
- projectId: string,
421
- mergeRequestIid: string,
422
- options: GitLabRequestOptions = {}
423
- ): Promise<unknown> {
424
- return this.get(
425
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/versions`,
426
- options
427
- );
428
- }
429
-
430
- getMergeRequestVersion(
431
- projectId: string,
432
- mergeRequestIid: string,
433
- versionId: string,
434
- options: GitLabRequestOptions = {}
435
- ): Promise<unknown> {
436
- return this.get(
437
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/versions/${encode(versionId)}`,
438
- options
439
- );
440
- }
441
-
442
- approveMergeRequest(
443
- projectId: string,
444
- mergeRequestIid: string,
445
- payload: Record<string, unknown> = {},
446
- options: GitLabRequestOptions = {}
447
- ): Promise<unknown> {
448
- return this.post(
449
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/approve`,
450
- {
451
- ...options,
452
- body: JSON.stringify(payload),
453
- headers: {
454
- "Content-Type": "application/json",
455
- ...(options.headers ?? {})
456
- }
457
- }
458
- );
459
- }
460
-
461
- unapproveMergeRequest(
462
- projectId: string,
463
- mergeRequestIid: string,
464
- options: GitLabRequestOptions = {}
465
- ): Promise<unknown> {
466
- return this.post(
467
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/unapprove`,
468
- {
469
- ...options,
470
- body: JSON.stringify({}),
471
- headers: {
472
- "Content-Type": "application/json",
473
- ...(options.headers ?? {})
474
- }
475
- }
476
- );
477
- }
478
-
479
- getMergeRequestApprovalState(
480
- projectId: string,
481
- mergeRequestIid: string,
482
- options: GitLabRequestOptions = {}
483
- ): Promise<unknown> {
484
- return this.get(
485
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/approval_state`,
486
- options
487
- );
488
- }
489
-
490
- listMergeRequestDiscussions(
491
- projectId: string,
492
- mergeRequestIid: string,
493
- options: GitLabRequestOptions = {}
494
- ): Promise<unknown> {
495
- return this.get(
496
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/discussions`,
497
- options
498
- );
499
- }
500
-
501
- createMergeRequestDiscussionNote(
502
- projectId: string,
503
- mergeRequestIid: string,
504
- discussionId: string,
505
- payload: {
506
- body: string;
507
- created_at?: string;
508
- },
509
- options: GitLabRequestOptions = {}
510
- ): Promise<unknown> {
511
- return this.post(
512
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/discussions/${encode(discussionId)}/notes`,
513
- {
514
- ...options,
515
- body: JSON.stringify(payload),
516
- headers: {
517
- "Content-Type": "application/json",
518
- ...(options.headers ?? {})
519
- }
520
- }
521
- );
522
- }
523
-
524
- createMergeRequestThread(
525
- projectId: string,
526
- mergeRequestIid: string,
527
- payload: {
528
- body: string;
529
- position?: Record<string, unknown>;
530
- created_at?: string;
531
- },
532
- options: GitLabRequestOptions = {}
533
- ): Promise<unknown> {
534
- return this.post(
535
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/discussions`,
536
- {
537
- ...options,
538
- body: JSON.stringify(payload),
539
- headers: {
540
- "Content-Type": "application/json",
541
- ...(options.headers ?? {})
542
- }
543
- }
544
- );
545
- }
546
-
547
- updateMergeRequestDiscussionNote(
548
- projectId: string,
549
- mergeRequestIid: string,
550
- discussionId: string,
551
- noteId: string,
552
- payload: {
553
- body?: string;
554
- resolved?: boolean;
555
- },
556
- options: GitLabRequestOptions = {}
557
- ): Promise<unknown> {
558
- return this.put(
559
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/discussions/${encode(discussionId)}/notes/${encode(noteId)}`,
560
- {
561
- ...options,
562
- body: JSON.stringify(payload),
563
- headers: {
564
- "Content-Type": "application/json",
565
- ...(options.headers ?? {})
566
- }
567
- }
568
- );
569
- }
570
-
571
- deleteMergeRequestDiscussionNote(
572
- projectId: string,
573
- mergeRequestIid: string,
574
- discussionId: string,
575
- noteId: string,
576
- options: GitLabRequestOptions = {}
577
- ): Promise<unknown> {
578
- return this.delete(
579
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/discussions/${encode(discussionId)}/notes/${encode(noteId)}`,
580
- options
581
- );
582
- }
583
-
584
- resolveMergeRequestThread(
585
- projectId: string,
586
- mergeRequestIid: string,
587
- discussionId: string,
588
- noteId: string,
589
- resolved: boolean,
590
- options: GitLabRequestOptions = {}
591
- ): Promise<unknown> {
592
- return this.put(
593
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/discussions/${encode(discussionId)}/notes/${encode(noteId)}`,
594
- {
595
- ...options,
596
- body: JSON.stringify({ resolved }),
597
- headers: {
598
- "Content-Type": "application/json",
599
- ...(options.headers ?? {})
600
- }
601
- }
602
- );
603
- }
604
-
605
- listMergeRequestNotes(
606
- projectId: string,
607
- mergeRequestIid: string,
608
- options: GitLabRequestOptions = {}
609
- ): Promise<unknown> {
610
- return this.get(
611
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/notes`,
612
- options
613
- );
614
- }
615
-
616
- getMergeRequestNote(
617
- projectId: string,
618
- mergeRequestIid: string,
619
- noteId: string,
620
- options: GitLabRequestOptions = {}
621
- ): Promise<unknown> {
622
- return this.get(
623
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/notes/${encode(noteId)}`,
624
- options
625
- );
626
- }
627
-
628
- createMergeRequestNote(
629
- projectId: string,
630
- mergeRequestIid: string,
631
- body: string,
632
- options: GitLabRequestOptions = {}
633
- ): Promise<unknown> {
634
- return this.post(
635
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/notes`,
636
- {
637
- ...options,
638
- body: JSON.stringify({ body }),
639
- headers: {
640
- "Content-Type": "application/json",
641
- ...(options.headers ?? {})
642
- }
643
- }
644
- );
645
- }
646
-
647
- getDraftNote(
648
- projectId: string,
649
- mergeRequestIid: string,
650
- draftNoteId: string,
651
- options: GitLabRequestOptions = {}
652
- ): Promise<unknown> {
653
- return this.get(
654
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/draft_notes/${encode(draftNoteId)}`,
655
- options
656
- );
657
- }
658
-
659
- listDraftNotes(
660
- projectId: string,
661
- mergeRequestIid: string,
662
- options: GitLabRequestOptions = {}
663
- ): Promise<unknown> {
664
- return this.get(
665
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/draft_notes`,
666
- options
667
- );
668
- }
669
-
670
- createDraftNote(
671
- projectId: string,
672
- mergeRequestIid: string,
673
- payload: {
674
- body: string;
675
- position?: Record<string, unknown>;
676
- resolve_discussion?: boolean;
677
- },
678
- options: GitLabRequestOptions = {}
679
- ): Promise<unknown> {
680
- return this.post(
681
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/draft_notes`,
682
- {
683
- ...options,
684
- body: JSON.stringify({
685
- note: payload.body,
686
- position: payload.position,
687
- resolve_discussion: payload.resolve_discussion
688
- }),
689
- headers: {
690
- "Content-Type": "application/json",
691
- ...(options.headers ?? {})
692
- }
693
- }
694
- );
695
- }
696
-
697
- updateDraftNote(
698
- projectId: string,
699
- mergeRequestIid: string,
700
- draftNoteId: string,
701
- payload: {
702
- body?: string;
703
- position?: Record<string, unknown>;
704
- resolve_discussion?: boolean;
705
- },
706
- options: GitLabRequestOptions = {}
707
- ): Promise<unknown> {
708
- return this.put(
709
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/draft_notes/${encode(draftNoteId)}`,
710
- {
711
- ...options,
712
- body: JSON.stringify({
713
- note: payload.body,
714
- position: payload.position,
715
- resolve_discussion: payload.resolve_discussion
716
- }),
717
- headers: {
718
- "Content-Type": "application/json",
719
- ...(options.headers ?? {})
720
- }
721
- }
722
- );
723
- }
724
-
725
- deleteDraftNote(
726
- projectId: string,
727
- mergeRequestIid: string,
728
- draftNoteId: string,
729
- options: GitLabRequestOptions = {}
730
- ): Promise<unknown> {
731
- return this.delete(
732
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/draft_notes/${encode(draftNoteId)}`,
733
- options
734
- );
735
- }
736
-
737
- publishDraftNote(
738
- projectId: string,
739
- mergeRequestIid: string,
740
- draftNoteId: string,
741
- options: GitLabRequestOptions = {}
742
- ): Promise<unknown> {
743
- return this.put(
744
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/draft_notes/${encode(draftNoteId)}/publish`,
745
- {
746
- ...options,
747
- body: JSON.stringify({}),
748
- headers: {
749
- "Content-Type": "application/json",
750
- ...(options.headers ?? {})
751
- }
752
- }
753
- );
754
- }
755
-
756
- bulkPublishDraftNotes(
757
- projectId: string,
758
- mergeRequestIid: string,
759
- options: GitLabRequestOptions = {}
760
- ): Promise<unknown> {
761
- return this.post(
762
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/draft_notes/bulk_publish`,
763
- {
764
- ...options,
765
- body: JSON.stringify({}),
766
- headers: {
767
- "Content-Type": "application/json",
768
- ...(options.headers ?? {})
769
- }
770
- }
771
- );
772
- }
773
-
774
- createNote(
775
- projectId: string,
776
- noteableType: "issue" | "merge_request",
777
- noteableIid: string,
778
- body: string,
779
- options: GitLabRequestOptions = {}
780
- ): Promise<unknown> {
781
- return this.post(
782
- `/projects/${encode(projectId)}/${noteableType}s/${encode(noteableIid)}/notes`,
783
- {
784
- ...options,
785
- body: JSON.stringify({ body }),
786
- headers: {
787
- "Content-Type": "application/json",
788
- ...(options.headers ?? {})
789
- }
790
- }
791
- );
792
- }
793
-
794
- updateMergeRequestNote(
795
- projectId: string,
796
- mergeRequestIid: string,
797
- noteId: string,
798
- body: string,
799
- options: GitLabRequestOptions = {}
800
- ): Promise<unknown> {
801
- return this.put(
802
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/notes/${encode(noteId)}`,
803
- {
804
- ...options,
805
- body: JSON.stringify({ body }),
806
- headers: {
807
- "Content-Type": "application/json",
808
- ...(options.headers ?? {})
809
- }
810
- }
811
- );
812
- }
813
-
814
- deleteMergeRequestNote(
815
- projectId: string,
816
- mergeRequestIid: string,
817
- noteId: string,
818
- options: GitLabRequestOptions = {}
819
- ): Promise<unknown> {
820
- return this.delete(
821
- `/projects/${encode(projectId)}/merge_requests/${encode(mergeRequestIid)}/notes/${encode(noteId)}`,
822
- options
823
- );
824
- }
825
-
826
- // issues
827
- listIssues(projectId: string, options: GitLabRequestOptions = {}): Promise<unknown> {
828
- return this.get(`/projects/${encode(projectId)}/issues`, options);
829
- }
830
-
831
- listGlobalIssues(options: GitLabRequestOptions = {}): Promise<unknown> {
832
- return this.get("/issues", options);
833
- }
834
-
835
- getIssue(
836
- projectId: string,
837
- issueIid: string,
838
- options: GitLabRequestOptions = {}
839
- ): Promise<unknown> {
840
- return this.get(`/projects/${encode(projectId)}/issues/${encode(issueIid)}`, options);
841
- }
842
-
843
- createIssue(
844
- projectId: string,
845
- payload: {
846
- title: string;
847
- description?: string;
848
- assignee_ids?: number[];
849
- labels?: string;
850
- milestone_id?: number;
851
- due_date?: string;
852
- confidential?: boolean;
853
- issue_type?: string;
854
- },
855
- options: GitLabRequestOptions = {}
856
- ): Promise<unknown> {
857
- return this.post(`/projects/${encode(projectId)}/issues`, {
858
- ...options,
859
- body: JSON.stringify(payload),
860
- headers: {
861
- "Content-Type": "application/json",
862
- ...(options.headers ?? {})
863
- }
864
- });
865
- }
866
-
867
- updateIssue(
868
- projectId: string,
869
- issueIid: string,
870
- payload: Record<string, unknown>,
871
- options: GitLabRequestOptions = {}
872
- ): Promise<unknown> {
873
- return this.put(`/projects/${encode(projectId)}/issues/${encode(issueIid)}`, {
874
- ...options,
875
- body: JSON.stringify(payload),
876
- headers: {
877
- "Content-Type": "application/json",
878
- ...(options.headers ?? {})
879
- }
880
- });
881
- }
882
-
883
- deleteIssue(
884
- projectId: string,
885
- issueIid: string,
886
- options: GitLabRequestOptions = {}
887
- ): Promise<unknown> {
888
- return this.delete(`/projects/${encode(projectId)}/issues/${encode(issueIid)}`, options);
889
- }
890
-
891
- myIssues(
892
- payload: {
893
- project_id?: string;
894
- state?: "opened" | "closed" | "all";
895
- labels?: string;
896
- milestone?: string;
897
- search?: string;
898
- created_after?: string;
899
- created_before?: string;
900
- updated_after?: string;
901
- updated_before?: string;
902
- per_page?: number;
903
- page?: number;
904
- scope?: string;
905
- },
906
- options: GitLabRequestOptions = {}
907
- ): Promise<unknown> {
908
- const { project_id: projectId, ...queryPayload } = payload;
909
- const path = projectId ? `/projects/${encode(projectId)}/issues` : "/issues";
910
-
911
- return this.get(path, {
912
- ...options,
913
- query: {
914
- scope: queryPayload.scope ?? "assigned_to_me",
915
- ...queryPayload,
916
- ...(options.query ?? {})
917
- }
918
- });
919
- }
920
-
921
- listIssueDiscussions(
922
- projectId: string,
923
- issueIid: string,
924
- options: GitLabRequestOptions = {}
925
- ): Promise<unknown> {
926
- return this.get(
927
- `/projects/${encode(projectId)}/issues/${encode(issueIid)}/discussions`,
928
- options
929
- );
930
- }
931
-
932
- createIssueNote(
933
- projectId: string,
934
- issueIid: string,
935
- payload: {
936
- body: string;
937
- discussion_id?: string;
938
- created_at?: string;
939
- },
940
- options: GitLabRequestOptions = {}
941
- ): Promise<unknown> {
942
- const discussionPath = payload.discussion_id
943
- ? `/discussions/${encode(payload.discussion_id)}/notes`
944
- : "/notes";
945
- return this.post(`/projects/${encode(projectId)}/issues/${encode(issueIid)}${discussionPath}`, {
946
- ...options,
947
- body: JSON.stringify({
948
- body: payload.body,
949
- created_at: payload.created_at
950
- }),
951
- headers: {
952
- "Content-Type": "application/json",
953
- ...(options.headers ?? {})
954
- }
955
- });
956
- }
957
-
958
- updateIssueNote(
959
- projectId: string,
960
- issueIid: string,
961
- discussionId: string,
962
- noteId: string,
963
- payload: {
964
- body?: string;
965
- resolved?: boolean;
966
- },
967
- options: GitLabRequestOptions = {}
968
- ): Promise<unknown> {
969
- return this.put(
970
- `/projects/${encode(projectId)}/issues/${encode(issueIid)}/discussions/${encode(discussionId)}/notes/${encode(noteId)}`,
971
- {
972
- ...options,
973
- body: JSON.stringify(payload),
974
- headers: {
975
- "Content-Type": "application/json",
976
- ...(options.headers ?? {})
977
- }
978
- }
979
- );
980
- }
981
-
982
- listIssueLinks(
983
- projectId: string,
984
- issueIid: string,
985
- options: GitLabRequestOptions = {}
986
- ): Promise<unknown> {
987
- return this.get(`/projects/${encode(projectId)}/issues/${encode(issueIid)}/links`, options);
988
- }
989
-
990
- getIssueLink(
991
- projectId: string,
992
- issueIid: string,
993
- issueLinkId: string,
994
- options: GitLabRequestOptions = {}
995
- ): Promise<unknown> {
996
- return this.get(
997
- `/projects/${encode(projectId)}/issues/${encode(issueIid)}/links/${encode(issueLinkId)}`,
998
- options
999
- );
1000
- }
1001
-
1002
- createIssueLink(
1003
- projectId: string,
1004
- issueIid: string,
1005
- payload: {
1006
- target_project_id: string;
1007
- target_issue_iid: string;
1008
- link_type?: "relates_to" | "blocks" | "is_blocked_by";
1009
- },
1010
- options: GitLabRequestOptions = {}
1011
- ): Promise<unknown> {
1012
- return this.post(`/projects/${encode(projectId)}/issues/${encode(issueIid)}/links`, {
1013
- ...options,
1014
- body: JSON.stringify(payload),
1015
- headers: {
1016
- "Content-Type": "application/json",
1017
- ...(options.headers ?? {})
1018
- }
1019
- });
1020
- }
1021
-
1022
- deleteIssueLink(
1023
- projectId: string,
1024
- issueIid: string,
1025
- issueLinkId: string,
1026
- options: GitLabRequestOptions = {}
1027
- ): Promise<unknown> {
1028
- return this.delete(
1029
- `/projects/${encode(projectId)}/issues/${encode(issueIid)}/links/${encode(issueLinkId)}`,
1030
- options
1031
- );
1032
- }
1033
-
1034
- // wiki
1035
- listWikiPages(projectId: string, options: GitLabRequestOptions = {}): Promise<unknown> {
1036
- return this.get(`/projects/${encode(projectId)}/wikis`, options);
1037
- }
1038
-
1039
- getWikiPage(
1040
- projectId: string,
1041
- slug: string,
1042
- options: GitLabRequestOptions = {}
1043
- ): Promise<unknown> {
1044
- return this.get(`/projects/${encode(projectId)}/wikis/${encode(slug)}`, options);
1045
- }
1046
-
1047
- createWikiPage(
1048
- projectId: string,
1049
- payload: {
1050
- title: string;
1051
- content: string;
1052
- format?: "markdown" | "rdoc" | "asciidoc" | "org";
1053
- },
1054
- options: GitLabRequestOptions = {}
1055
- ): Promise<unknown> {
1056
- return this.post(`/projects/${encode(projectId)}/wikis`, {
1057
- ...options,
1058
- body: JSON.stringify(payload),
1059
- headers: {
1060
- "Content-Type": "application/json",
1061
- ...(options.headers ?? {})
1062
- }
1063
- });
1064
- }
1065
-
1066
- updateWikiPage(
1067
- projectId: string,
1068
- slug: string,
1069
- payload: {
1070
- content: string;
1071
- title?: string;
1072
- format?: "markdown" | "rdoc" | "asciidoc" | "org";
1073
- },
1074
- options: GitLabRequestOptions = {}
1075
- ): Promise<unknown> {
1076
- return this.put(`/projects/${encode(projectId)}/wikis/${encode(slug)}`, {
1077
- ...options,
1078
- body: JSON.stringify(payload),
1079
- headers: {
1080
- "Content-Type": "application/json",
1081
- ...(options.headers ?? {})
1082
- }
1083
- });
1084
- }
1085
-
1086
- deleteWikiPage(
1087
- projectId: string,
1088
- slug: string,
1089
- options: GitLabRequestOptions = {}
1090
- ): Promise<unknown> {
1091
- return this.delete(`/projects/${encode(projectId)}/wikis/${encode(slug)}`, options);
1092
- }
1093
-
1094
- // pipelines
1095
- listPipelines(projectId: string, options: GitLabRequestOptions = {}): Promise<unknown> {
1096
- return this.get(`/projects/${encode(projectId)}/pipelines`, options);
1097
- }
1098
-
1099
- getPipeline(
1100
- projectId: string,
1101
- pipelineId: string,
1102
- options: GitLabRequestOptions = {}
1103
- ): Promise<unknown> {
1104
- return this.get(`/projects/${encode(projectId)}/pipelines/${encode(pipelineId)}`, options);
1105
- }
1106
-
1107
- listPipelineJobs(
1108
- projectId: string,
1109
- pipelineId: string,
1110
- options: GitLabRequestOptions = {}
1111
- ): Promise<unknown> {
1112
- return this.get(`/projects/${encode(projectId)}/pipelines/${encode(pipelineId)}/jobs`, options);
1113
- }
1114
-
1115
- listPipelineTriggerJobs(
1116
- projectId: string,
1117
- pipelineId: string,
1118
- options: GitLabRequestOptions = {}
1119
- ): Promise<unknown> {
1120
- return this.get(
1121
- `/projects/${encode(projectId)}/pipelines/${encode(pipelineId)}/bridges`,
1122
- options
1123
- );
1124
- }
1125
-
1126
- getPipelineJob(
1127
- projectId: string,
1128
- jobId: string,
1129
- options: GitLabRequestOptions = {}
1130
- ): Promise<unknown> {
1131
- return this.get(`/projects/${encode(projectId)}/jobs/${encode(jobId)}`, options);
1132
- }
1133
-
1134
- getPipelineJobOutput(
1135
- projectId: string,
1136
- jobId: string,
1137
- options: GitLabRequestOptions = {}
1138
- ): Promise<unknown> {
1139
- return this.get(`/projects/${encode(projectId)}/jobs/${encode(jobId)}/trace`, options);
1140
- }
1141
-
1142
- createPipeline(
1143
- projectId: string,
1144
- payload: {
1145
- ref: string;
1146
- variables?: Array<{ key: string; value: string; variable_type?: "env_var" | "file" }>;
1147
- },
1148
- options: GitLabRequestOptions = {}
1149
- ): Promise<unknown> {
1150
- return this.post(`/projects/${encode(projectId)}/pipeline`, {
1151
- ...options,
1152
- body: JSON.stringify(payload),
1153
- headers: {
1154
- "Content-Type": "application/json",
1155
- ...(options.headers ?? {})
1156
- }
1157
- });
1158
- }
1159
-
1160
- retryPipeline(
1161
- projectId: string,
1162
- pipelineId: string,
1163
- options: GitLabRequestOptions = {}
1164
- ): Promise<unknown> {
1165
- return this.post(
1166
- `/projects/${encode(projectId)}/pipelines/${encode(pipelineId)}/retry`,
1167
- options
1168
- );
1169
- }
1170
-
1171
- cancelPipeline(
1172
- projectId: string,
1173
- pipelineId: string,
1174
- options: GitLabRequestOptions = {}
1175
- ): Promise<unknown> {
1176
- return this.post(
1177
- `/projects/${encode(projectId)}/pipelines/${encode(pipelineId)}/cancel`,
1178
- options
1179
- );
1180
- }
1181
-
1182
- retryPipelineJob(
1183
- projectId: string,
1184
- jobId: string,
1185
- options: GitLabRequestOptions = {}
1186
- ): Promise<unknown> {
1187
- return this.post(`/projects/${encode(projectId)}/jobs/${encode(jobId)}/retry`, options);
1188
- }
1189
-
1190
- cancelPipelineJob(
1191
- projectId: string,
1192
- jobId: string,
1193
- options: GitLabRequestOptions = {}
1194
- ): Promise<unknown> {
1195
- return this.post(`/projects/${encode(projectId)}/jobs/${encode(jobId)}/cancel`, options);
1196
- }
1197
-
1198
- playPipelineJob(
1199
- projectId: string,
1200
- jobId: string,
1201
- options: GitLabRequestOptions = {}
1202
- ): Promise<unknown> {
1203
- return this.post(`/projects/${encode(projectId)}/jobs/${encode(jobId)}/play`, options);
1204
- }
1205
-
1206
- // milestones
1207
- listMilestones(projectId: string, options: GitLabRequestOptions = {}): Promise<unknown> {
1208
- return this.get(`/projects/${encode(projectId)}/milestones`, options);
1209
- }
1210
-
1211
- getMilestone(
1212
- projectId: string,
1213
- milestoneId: string,
1214
- options: GitLabRequestOptions = {}
1215
- ): Promise<unknown> {
1216
- return this.get(`/projects/${encode(projectId)}/milestones/${encode(milestoneId)}`, options);
1217
- }
1218
-
1219
- createMilestone(
1220
- projectId: string,
1221
- payload: {
1222
- title: string;
1223
- description?: string;
1224
- due_date?: string;
1225
- start_date?: string;
1226
- },
1227
- options: GitLabRequestOptions = {}
1228
- ): Promise<unknown> {
1229
- return this.post(`/projects/${encode(projectId)}/milestones`, {
1230
- ...options,
1231
- body: JSON.stringify(payload),
1232
- headers: {
1233
- "Content-Type": "application/json",
1234
- ...(options.headers ?? {})
1235
- }
1236
- });
1237
- }
1238
-
1239
- updateMilestone(
1240
- projectId: string,
1241
- milestoneId: string,
1242
- payload: Record<string, unknown>,
1243
- options: GitLabRequestOptions = {}
1244
- ): Promise<unknown> {
1245
- return this.put(`/projects/${encode(projectId)}/milestones/${encode(milestoneId)}`, {
1246
- ...options,
1247
- body: JSON.stringify(payload),
1248
- headers: {
1249
- "Content-Type": "application/json",
1250
- ...(options.headers ?? {})
1251
- }
1252
- });
1253
- }
1254
-
1255
- deleteMilestone(
1256
- projectId: string,
1257
- milestoneId: string,
1258
- options: GitLabRequestOptions = {}
1259
- ): Promise<unknown> {
1260
- return this.delete(`/projects/${encode(projectId)}/milestones/${encode(milestoneId)}`, options);
1261
- }
1262
-
1263
- getMilestoneIssues(
1264
- projectId: string,
1265
- milestoneId: string,
1266
- options: GitLabRequestOptions = {}
1267
- ): Promise<unknown> {
1268
- return this.get(
1269
- `/projects/${encode(projectId)}/milestones/${encode(milestoneId)}/issues`,
1270
- options
1271
- );
1272
- }
1273
-
1274
- getMilestoneMergeRequests(
1275
- projectId: string,
1276
- milestoneId: string,
1277
- options: GitLabRequestOptions = {}
1278
- ): Promise<unknown> {
1279
- return this.get(
1280
- `/projects/${encode(projectId)}/milestones/${encode(milestoneId)}/merge_requests`,
1281
- options
1282
- );
1283
- }
1284
-
1285
- promoteMilestone(
1286
- projectId: string,
1287
- milestoneId: string,
1288
- options: GitLabRequestOptions = {}
1289
- ): Promise<unknown> {
1290
- return this.post(
1291
- `/projects/${encode(projectId)}/milestones/${encode(milestoneId)}/promote`,
1292
- options
1293
- );
1294
- }
1295
-
1296
- getMilestoneBurndownEvents(
1297
- projectId: string,
1298
- milestoneId: string,
1299
- options: GitLabRequestOptions = {}
1300
- ): Promise<unknown> {
1301
- return this.get(
1302
- `/projects/${encode(projectId)}/milestones/${encode(milestoneId)}/burndown_events`,
1303
- options
1304
- );
1305
- }
1306
-
1307
- // releases
1308
- listReleases(projectId: string, options: GitLabRequestOptions = {}): Promise<unknown> {
1309
- return this.get(`/projects/${encode(projectId)}/releases`, options);
1310
- }
1311
-
1312
- getRelease(
1313
- projectId: string,
1314
- tagName: string,
1315
- options: GitLabRequestOptions = {}
1316
- ): Promise<unknown> {
1317
- return this.get(`/projects/${encode(projectId)}/releases/${encode(tagName)}`, options);
1318
- }
1319
-
1320
- createRelease(
1321
- projectId: string,
1322
- payload: Record<string, unknown>,
1323
- options: GitLabRequestOptions = {}
1324
- ): Promise<unknown> {
1325
- return this.post(`/projects/${encode(projectId)}/releases`, {
1326
- ...options,
1327
- body: JSON.stringify(payload),
1328
- headers: {
1329
- "Content-Type": "application/json",
1330
- ...(options.headers ?? {})
1331
- }
1332
- });
1333
- }
1334
-
1335
- updateRelease(
1336
- projectId: string,
1337
- tagName: string,
1338
- payload: Record<string, unknown>,
1339
- options: GitLabRequestOptions = {}
1340
- ): Promise<unknown> {
1341
- return this.put(`/projects/${encode(projectId)}/releases/${encode(tagName)}`, {
1342
- ...options,
1343
- body: JSON.stringify(payload),
1344
- headers: {
1345
- "Content-Type": "application/json",
1346
- ...(options.headers ?? {})
1347
- }
1348
- });
1349
- }
1350
-
1351
- deleteRelease(
1352
- projectId: string,
1353
- tagName: string,
1354
- options: GitLabRequestOptions = {}
1355
- ): Promise<unknown> {
1356
- return this.delete(`/projects/${encode(projectId)}/releases/${encode(tagName)}`, options);
1357
- }
1358
-
1359
- createReleaseEvidence(
1360
- projectId: string,
1361
- tagName: string,
1362
- options: GitLabRequestOptions = {}
1363
- ): Promise<unknown> {
1364
- return this.post(
1365
- `/projects/${encode(projectId)}/releases/${encode(tagName)}/evidence`,
1366
- options
1367
- );
1368
- }
1369
-
1370
- downloadReleaseAsset(
1371
- projectId: string,
1372
- tagName: string,
1373
- directAssetPath: string,
1374
- options: GitLabRequestOptions = {}
1375
- ): Promise<unknown> {
1376
- const safePath = encodeSlashPath(directAssetPath);
1377
- return this.get(
1378
- `/projects/${encode(projectId)}/releases/${encode(tagName)}/downloads/${safePath}`,
1379
- options
1380
- );
1381
- }
1382
-
1383
- // labels
1384
- listLabels(projectId: string, options: GitLabRequestOptions = {}): Promise<unknown> {
1385
- return this.get(`/projects/${encode(projectId)}/labels`, options);
1386
- }
1387
-
1388
- getLabel(
1389
- projectId: string,
1390
- labelId: string,
1391
- options: GitLabRequestOptions = {}
1392
- ): Promise<unknown> {
1393
- return this.get(`/projects/${encode(projectId)}/labels/${encode(labelId)}`, options);
1394
- }
1395
-
1396
- createLabel(
1397
- projectId: string,
1398
- payload: Record<string, unknown>,
1399
- options: GitLabRequestOptions = {}
1400
- ): Promise<unknown> {
1401
- return this.post(`/projects/${encode(projectId)}/labels`, {
1402
- ...options,
1403
- body: JSON.stringify(payload),
1404
- headers: {
1405
- "Content-Type": "application/json",
1406
- ...(options.headers ?? {})
1407
- }
1408
- });
1409
- }
1410
-
1411
- updateLabel(
1412
- projectId: string,
1413
- payload: Record<string, unknown>,
1414
- options: GitLabRequestOptions = {}
1415
- ): Promise<unknown> {
1416
- return this.put(`/projects/${encode(projectId)}/labels`, {
1417
- ...options,
1418
- body: JSON.stringify(payload),
1419
- headers: {
1420
- "Content-Type": "application/json",
1421
- ...(options.headers ?? {})
1422
- }
1423
- });
1424
- }
1425
-
1426
- deleteLabel(
1427
- projectId: string,
1428
- labelName: string,
1429
- options: GitLabRequestOptions = {}
1430
- ): Promise<unknown> {
1431
- return this.delete(`/projects/${encode(projectId)}/labels`, {
1432
- ...options,
1433
- query: {
1434
- name: labelName,
1435
- ...(options.query ?? {})
1436
- }
1437
- });
1438
- }
1439
-
1440
- // namespaces/users/events
1441
- listNamespaces(options: GitLabRequestOptions = {}): Promise<unknown> {
1442
- return this.get("/namespaces", options);
1443
- }
1444
-
1445
- listGroupIterations(groupId: string, options: GitLabRequestOptions = {}): Promise<unknown> {
1446
- return this.get(`/groups/${encode(groupId)}/iterations`, options);
1447
- }
1448
-
1449
- getNamespace(namespaceIdOrPath: string, options: GitLabRequestOptions = {}): Promise<unknown> {
1450
- return this.get(`/namespaces/${encode(namespaceIdOrPath)}`, options);
1451
- }
1452
-
1453
- verifyNamespace(pathName: string, options: GitLabRequestOptions = {}): Promise<unknown> {
1454
- return this.get(`/namespaces/${encode(pathName)}/exists`, options);
1455
- }
1456
-
1457
- getUsers(options: GitLabRequestOptions = {}): Promise<unknown> {
1458
- return this.get("/users", options);
1459
- }
1460
-
1461
- listEvents(options: GitLabRequestOptions = {}): Promise<unknown> {
1462
- return this.get("/events", options);
1463
- }
1464
-
1465
- getProjectEvents(projectId: string, options: GitLabRequestOptions = {}): Promise<unknown> {
1466
- return this.get(`/projects/${encode(projectId)}/events`, options);
1467
- }
1468
-
1469
- // attachments / markdown
1470
- uploadMarkdown(
1471
- projectId: string,
1472
- content: string,
1473
- filename: string,
1474
- options: GitLabRequestOptions = {}
1475
- ): Promise<unknown> {
1476
- const form = new FormData();
1477
- form.append("file", new Blob([content], { type: "text/markdown" }), filename);
1478
-
1479
- return this.post(`/projects/${encode(projectId)}/uploads`, {
1480
- ...options,
1481
- body: form,
1482
- headers: {
1483
- Accept: "*/*",
1484
- ...(options.headers ?? {})
1485
- }
1486
- });
1487
- }
1488
-
1489
- async uploadMarkdownFile(
1490
- projectId: string,
1491
- filePath: string,
1492
- options: GitLabRequestOptions = {}
1493
- ): Promise<unknown> {
1494
- const content = await fs.readFile(filePath);
1495
- const filename = path.basename(filePath);
1496
- const form = new FormData();
1497
- form.append("file", new Blob([content], { type: "application/octet-stream" }), filename);
1498
-
1499
- return this.post(`/projects/${encode(projectId)}/uploads`, {
1500
- ...options,
1501
- body: form,
1502
- headers: {
1503
- Accept: "*/*",
1504
- ...(options.headers ?? {})
1505
- }
1506
- });
1507
- }
1508
-
1509
- async downloadAttachment(
1510
- urlOrPath: string,
1511
- options: GitLabRequestOptions = {}
1512
- ): Promise<{ fileName: string; contentType: string; base64: string }> {
1513
- const requestConfig = this.resolveRequestConfig(options);
1514
- const url = this.resolveAttachmentUrl(urlOrPath, requestConfig.apiUrl);
1515
-
1516
- let headers = new Headers(options.headers);
1517
- let token = requestConfig.token;
1518
- let fetchImpl: typeof fetch = fetch;
1519
-
1520
- if (this.beforeRequest) {
1521
- const override = await this.beforeRequest({
1522
- url,
1523
- method: "GET",
1524
- headers,
1525
- token
1526
- });
1527
-
1528
- if (override?.headers) {
1529
- headers = override.headers;
1530
- }
1531
- if (override?.token !== undefined) {
1532
- token = override.token;
1533
- }
1534
- if (override?.fetchImpl) {
1535
- fetchImpl = override.fetchImpl;
1536
- }
1537
- }
1538
-
1539
- this.attachAuth(headers, token);
1540
-
1541
- const response = await fetchImpl(url, {
1542
- method: "GET",
1543
- headers,
1544
- signal: AbortSignal.timeout(this.timeoutMs)
1545
- });
1546
-
1547
- if (!response.ok) {
1548
- throw new GitLabApiError(
1549
- `GitLab attachment download failed: ${response.status} ${response.statusText}`,
1550
- response.status,
1551
- await this.parseResponseBody(response)
1552
- );
1553
- }
1554
-
1555
- const contentType = response.headers.get("content-type") ?? "application/octet-stream";
1556
- const disposition = response.headers.get("content-disposition") ?? "";
1557
- const fileName = extractFileName(disposition) ?? `attachment-${Date.now()}`;
1558
- const bytes = Buffer.from(await response.arrayBuffer());
1559
-
1560
- return {
1561
- fileName,
1562
- contentType,
1563
- base64: bytes.toString("base64")
1564
- };
1565
- }
1566
-
1567
- // graphql
1568
- executeGraphql(
1569
- query: string,
1570
- variables: Record<string, unknown> | undefined,
1571
- options: GitLabRequestOptions = {}
1572
- ): Promise<unknown> {
1573
- const requestConfig = this.resolveRequestConfig(options);
1574
- const endpoint = buildGraphqlEndpoint(requestConfig.apiUrl);
1575
-
1576
- return this.rawRequest(endpoint, {
1577
- method: "POST",
1578
- headers: {
1579
- "Content-Type": "application/json",
1580
- ...(options.headers ?? {})
1581
- },
1582
- body: JSON.stringify({ query, variables }),
1583
- token: requestConfig.token
1584
- });
1585
- }
1586
-
1587
- // generic methods
1588
- get(path: string, options: GitLabRequestOptions = {}): Promise<unknown> {
1589
- return this.request("GET", path, options);
1590
- }
1591
-
1592
- post(path: string, options: GitLabRequestOptions = {}): Promise<unknown> {
1593
- return this.request("POST", path, options);
1594
- }
1595
-
1596
- put(path: string, options: GitLabRequestOptions = {}): Promise<unknown> {
1597
- return this.request("PUT", path, options);
1598
- }
1599
-
1600
- delete(path: string, options: GitLabRequestOptions = {}): Promise<unknown> {
1601
- return this.request("DELETE", path, options);
1602
- }
1603
-
1604
- private async request(
1605
- method: "GET" | "POST" | "PUT" | "DELETE",
1606
- path: string,
1607
- options: GitLabRequestOptions = {}
1608
- ): Promise<unknown> {
1609
- const config = this.resolveRequestConfig(options);
1610
- const url = new URL(path.replace(/^\//, ""), `${config.apiUrl}/`);
1611
-
1612
- for (const [key, value] of Object.entries(options.query ?? {})) {
1613
- if (value !== undefined && value !== null) {
1614
- url.searchParams.set(key, String(value));
1615
- }
1616
- }
1617
-
1618
- return this.rawRequest(url, {
1619
- method,
1620
- body: options.body,
1621
- headers: options.headers,
1622
- token: config.token
1623
- });
1624
- }
1625
-
1626
- private async rawRequest(
1627
- url: URL,
1628
- options: {
1629
- method: string;
1630
- body?: BodyInit;
1631
- headers?: HeadersInit;
1632
- token?: string;
1633
- }
1634
- ): Promise<unknown> {
1635
- let headers = new Headers(options.headers);
1636
- if (!headers.has("Accept")) {
1637
- headers.set("Accept", "application/json");
1638
- }
1639
- let requestBody = options.body;
1640
- let token = options.token;
1641
- let fetchImpl: typeof fetch = fetch;
1642
-
1643
- if (this.beforeRequest) {
1644
- const override = await this.beforeRequest({
1645
- url,
1646
- method: options.method,
1647
- headers,
1648
- body: requestBody,
1649
- token
1650
- });
1651
-
1652
- if (override?.headers) {
1653
- headers = override.headers;
1654
- }
1655
- if (override?.body !== undefined) {
1656
- requestBody = override.body;
1657
- }
1658
- if (override?.token !== undefined) {
1659
- token = override.token;
1660
- }
1661
- if (override?.fetchImpl) {
1662
- fetchImpl = override.fetchImpl;
1663
- }
1664
- }
1665
-
1666
- this.attachAuth(headers, token);
1667
-
1668
- const response = await fetchImpl(url, {
1669
- method: options.method,
1670
- body: requestBody,
1671
- headers,
1672
- signal: AbortSignal.timeout(this.timeoutMs)
1673
- });
1674
-
1675
- const body = await this.parseResponseBody(response);
1676
-
1677
- if (!response.ok) {
1678
- throw new GitLabApiError(
1679
- `GitLab API request failed: ${response.status} ${response.statusText}`,
1680
- response.status,
1681
- body
1682
- );
1683
- }
1684
-
1685
- return body;
1686
- }
1687
-
1688
- private async parseResponseBody(response: Response): Promise<unknown> {
1689
- const contentType = response.headers.get("content-type") ?? "";
1690
-
1691
- if (contentType.includes("application/json")) {
1692
- return response.json();
1693
- }
1694
-
1695
- return response.text();
1696
- }
1697
-
1698
- private resolveRequestConfig(options: GitLabRequestOptions): { apiUrl: string; token?: string } {
1699
- const sessionAuth = getSessionAuth();
1700
- const apiUrl = options.apiUrl ?? sessionAuth?.apiUrl ?? this.pickApiUrl();
1701
- const token = options.token ?? sessionAuth?.token ?? this.defaultToken;
1702
-
1703
- return {
1704
- apiUrl: normalizeApiUrl(apiUrl),
1705
- token
1706
- };
1707
- }
1708
-
1709
- private pickApiUrl(): string {
1710
- if (this.apiUrls.length <= 1) {
1711
- return this.baseApiUrl;
1712
- }
1713
-
1714
- const index = this.nextApiUrlIndex % this.apiUrls.length;
1715
- this.nextApiUrlIndex = (this.nextApiUrlIndex + 1) % this.apiUrls.length;
1716
- return this.apiUrls[index] ?? this.baseApiUrl;
1717
- }
1718
-
1719
- private resolveAbsoluteUrl(raw: string, apiUrl: string): URL {
1720
- if (/^https?:\/\//i.test(raw)) {
1721
- return new URL(raw);
1722
- }
1723
-
1724
- const base = new URL(apiUrl);
1725
- return new URL(raw.replace(/^\//, ""), `${base.origin}/`);
1726
- }
1727
-
1728
- private resolveAttachmentUrl(raw: string, apiUrl: string): URL {
1729
- const base = new URL(apiUrl);
1730
- const resolved = this.resolveAbsoluteUrl(raw, apiUrl);
1731
-
1732
- if (resolved.origin !== base.origin) {
1733
- throw new Error(
1734
- `Refusing to download cross-origin attachment URL '${resolved.origin}'. Only '${base.origin}' is allowed.`
1735
- );
1736
- }
1737
-
1738
- return resolved;
1739
- }
1740
-
1741
- private attachAuth(headers: Headers, token?: string): void {
1742
- if (!token) {
1743
- return;
1744
- }
1745
-
1746
- headers.set("PRIVATE-TOKEN", token);
1747
- }
1748
- }
1749
-
1750
- export function getEffectiveSessionAuth(
1751
- defaultToken?: string,
1752
- defaultApiUrl?: string
1753
- ): SessionAuth {
1754
- const auth = getSessionAuth();
1755
-
1756
- return {
1757
- token: auth?.token ?? defaultToken,
1758
- apiUrl: auth?.apiUrl ?? defaultApiUrl,
1759
- header: auth?.header,
1760
- sessionId: auth?.sessionId,
1761
- updatedAt: auth?.updatedAt ?? Date.now()
1762
- };
1763
- }
1764
-
1765
- function encode(value: string): string {
1766
- return encodeURIComponent(value);
1767
- }
1768
-
1769
- function encodeSlashPath(pathValue: string): string {
1770
- const trimmed = pathValue.replace(/^\/+/, "").trim();
1771
- if (!trimmed) {
1772
- return "";
1773
- }
1774
-
1775
- return trimmed
1776
- .split("/")
1777
- .filter((segment) => segment.length > 0)
1778
- .map((segment) => encode(segment))
1779
- .join("/");
1780
- }
1781
-
1782
- function normalizeApiUrl(rawUrl: string): string {
1783
- const url = new URL(rawUrl);
1784
- const pathname = url.pathname.replace(/\/+$/, "");
1785
-
1786
- if (pathname.endsWith("/api/v4")) {
1787
- url.pathname = pathname;
1788
- return url.toString();
1789
- }
1790
-
1791
- url.pathname = `${pathname}/api/v4`.replace(/\/\//g, "/");
1792
-
1793
- return url.toString();
1794
- }
1795
-
1796
- function buildGraphqlEndpoint(apiUrl: string): URL {
1797
- const url = new URL(apiUrl);
1798
- const prefix = url.pathname.replace(/\/api\/v4\/?$/, "");
1799
- return new URL(`${prefix || "/"}/api/graphql`, url.origin);
1800
- }
1801
-
1802
- function extractFileName(contentDisposition: string): string | undefined {
1803
- const quoted = /filename\*?=(?:UTF-8''|")?([^";]+)/i.exec(contentDisposition);
1804
-
1805
- if (!quoted) {
1806
- return undefined;
1807
- }
1808
-
1809
- return decodeURIComponent(quoted[1] ?? "");
1810
- }