@ttpears/gitlab-mcp-server 1.7.3 → 1.8.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.
@@ -192,6 +192,35 @@ export class GitLabGraphQLClient {
192
192
  const client = this.getClient(userConfig, requiresWrite);
193
193
  return this.executeWithRetry(() => client.request(query, variables), 'GraphQL query');
194
194
  }
195
+ async fetchAllPages(query, variables, connectionPath, options = {}) {
196
+ const maxItems = options.maxItems ?? 100;
197
+ const pageSize = Math.min(options.pageSize ?? 50, this.config.maxPageSize);
198
+ const allNodes = [];
199
+ let after = undefined;
200
+ let lastPageInfo = { hasNextPage: false, hasPreviousPage: false, startCursor: null, endCursor: null };
201
+ while (allNodes.length < maxItems) {
202
+ const remaining = maxItems - allNodes.length;
203
+ const currentPageSize = Math.min(pageSize, remaining);
204
+ const result = await this.query(query, { ...variables, first: currentPageSize, after }, options.userConfig);
205
+ // Navigate to the connection using the dot-separated path
206
+ const connection = connectionPath.split('.').reduce((obj, key) => obj?.[key], result);
207
+ if (!connection || !connection.nodes) {
208
+ break;
209
+ }
210
+ allNodes.push(...connection.nodes);
211
+ lastPageInfo = connection.pageInfo || lastPageInfo;
212
+ if (!lastPageInfo.hasNextPage || allNodes.length >= maxItems) {
213
+ break;
214
+ }
215
+ after = lastPageInfo.endCursor ?? undefined;
216
+ }
217
+ return {
218
+ nodes: allNodes,
219
+ totalFetched: allNodes.length,
220
+ hasMore: lastPageInfo.hasNextPage && allNodes.length >= maxItems,
221
+ pageInfo: lastPageInfo,
222
+ };
223
+ }
195
224
  async getCurrentUser(userConfig) {
196
225
  const query = gql `
197
226
  query getCurrentUser {
@@ -233,10 +262,10 @@ export class GitLabGraphQLClient {
233
262
  `;
234
263
  return this.query(query, { fullPath }, userConfig);
235
264
  }
236
- async getProjects(first = 20, after, userConfig) {
265
+ async getProjects(first = 20, after, fetchAll = false, userConfig, sort) {
237
266
  const query = gql `
238
- query getProjects($first: Int!, $after: String) {
239
- projects(first: $first, after: $after) {
267
+ query getProjects($first: Int!, $after: String, $sort: String) {
268
+ projects(first: $first, after: $after, sort: $sort) {
240
269
  pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
241
270
  nodes {
242
271
  id
@@ -249,13 +278,20 @@ export class GitLabGraphQLClient {
249
278
  }
250
279
  }
251
280
  `;
252
- return this.query(query, { first: Math.min(first, 50), after }, userConfig);
281
+ if (fetchAll) {
282
+ return this.fetchAllPages(query, { sort }, 'projects', {
283
+ maxItems: first,
284
+ pageSize: this.config.maxPageSize,
285
+ userConfig,
286
+ });
287
+ }
288
+ return this.query(query, { first: Math.min(first, this.config.maxPageSize), after, sort }, userConfig);
253
289
  }
254
- async getIssues(projectPath, first = 20, after, userConfig) {
290
+ async getIssues(projectPath, first = 20, after, fetchAll = false, userConfig, sort) {
255
291
  const query = gql `
256
- query getIssues($projectPath: ID!, $first: Int!, $after: String) {
292
+ query getIssues($projectPath: ID!, $first: Int!, $after: String, $sort: IssueSort) {
257
293
  project(fullPath: $projectPath) {
258
- issues(first: $first, after: $after) {
294
+ issues(first: $first, after: $after, sort: $sort) {
259
295
  pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
260
296
  nodes {
261
297
  id
@@ -271,13 +307,20 @@ export class GitLabGraphQLClient {
271
307
  }
272
308
  }
273
309
  `;
274
- return this.query(query, { projectPath, first: Math.min(first, 50), after }, userConfig);
310
+ if (fetchAll) {
311
+ return this.fetchAllPages(query, { projectPath, sort: sort || 'UPDATED_DESC' }, 'project.issues', {
312
+ maxItems: first,
313
+ pageSize: this.config.maxPageSize,
314
+ userConfig,
315
+ });
316
+ }
317
+ return this.query(query, { projectPath, first: Math.min(first, this.config.maxPageSize), after, sort: sort || 'UPDATED_DESC' }, userConfig);
275
318
  }
276
- async getMergeRequests(projectPath, first = 20, after, userConfig) {
319
+ async getMergeRequests(projectPath, first = 20, after, fetchAll = false, userConfig, sort) {
277
320
  const query = gql `
278
- query getMergeRequests($projectPath: ID!, $first: Int!, $after: String) {
321
+ query getMergeRequests($projectPath: ID!, $first: Int!, $after: String, $sort: MergeRequestSort) {
279
322
  project(fullPath: $projectPath) {
280
- mergeRequests(first: $first, after: $after) {
323
+ mergeRequests(first: $first, after: $after, sort: $sort) {
281
324
  pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
282
325
  nodes {
283
326
  id
@@ -296,7 +339,14 @@ export class GitLabGraphQLClient {
296
339
  }
297
340
  }
298
341
  `;
299
- return this.query(query, { projectPath, first: Math.min(first, 50), after }, userConfig);
342
+ if (fetchAll) {
343
+ return this.fetchAllPages(query, { projectPath, sort: sort || 'UPDATED_DESC' }, 'project.mergeRequests', {
344
+ maxItems: first,
345
+ pageSize: this.config.maxPageSize,
346
+ userConfig,
347
+ });
348
+ }
349
+ return this.query(query, { projectPath, first: Math.min(first, this.config.maxPageSize), after, sort: sort || 'UPDATED_DESC' }, userConfig);
300
350
  }
301
351
  async createIssue(projectPath, title, description, userConfig) {
302
352
  await this.introspectSchema(userConfig);
@@ -623,10 +673,11 @@ export class GitLabGraphQLClient {
623
673
  const fields = type.getFields();
624
674
  return Object.keys(fields);
625
675
  }
626
- async globalSearch(searchTerm, scope, userConfig) {
676
+ async globalSearch(searchTerm, first = 20, after, userConfig) {
627
677
  const query = gql `
628
- query globalSearch($search: String, $first: Int!) {
629
- projects(search: $search, first: $first) {
678
+ query globalSearch($search: String, $first: Int!, $after: String) {
679
+ projects(search: $search, first: $first, after: $after) {
680
+ pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
630
681
  nodes {
631
682
  id
632
683
  name
@@ -635,7 +686,8 @@ export class GitLabGraphQLClient {
635
686
  visibility
636
687
  }
637
688
  }
638
- issues(search: $search, first: $first) {
689
+ issues(search: $search, first: $first, after: $after) {
690
+ pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
639
691
  nodes {
640
692
  id
641
693
  iid
@@ -649,10 +701,34 @@ export class GitLabGraphQLClient {
649
701
  `;
650
702
  return this.query(query, {
651
703
  search: searchTerm || undefined,
652
- first: Math.min(this.config.maxPageSize, 25)
704
+ first: Math.min(first, this.config.maxPageSize),
705
+ after
653
706
  }, userConfig);
654
707
  }
655
- async searchProjects(searchTerm, first = 20, after, userConfig) {
708
+ async globalSearchAll(searchTerm, maxItems = 100, userConfig) {
709
+ const projectsQuery = gql `
710
+ query globalSearchProjects($search: String, $first: Int!, $after: String) {
711
+ projects(search: $search, first: $first, after: $after) {
712
+ pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
713
+ nodes { id name fullPath webUrl visibility }
714
+ }
715
+ }
716
+ `;
717
+ const issuesQuery = gql `
718
+ query globalSearchIssues($search: String, $first: Int!, $after: String) {
719
+ issues(search: $search, first: $first, after: $after) {
720
+ pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
721
+ nodes { id iid title state webUrl createdAt }
722
+ }
723
+ }
724
+ `;
725
+ const [projects, issues] = await Promise.all([
726
+ this.fetchAllPages(projectsQuery, { search: searchTerm || undefined }, 'projects', { maxItems, userConfig }),
727
+ this.fetchAllPages(issuesQuery, { search: searchTerm || undefined }, 'issues', { maxItems, userConfig }),
728
+ ]);
729
+ return { projects, issues };
730
+ }
731
+ async searchProjects(searchTerm, first = 20, after, fetchAll = false, userConfig) {
656
732
  const query = gql `
657
733
  query searchProjects($search: String!, $first: Int!, $after: String) {
658
734
  projects(search: $search, first: $first, after: $after) {
@@ -669,7 +745,14 @@ export class GitLabGraphQLClient {
669
745
  }
670
746
  }
671
747
  `;
672
- return this.query(query, { search: searchTerm, first: Math.min(first, 50), after }, userConfig);
748
+ if (fetchAll) {
749
+ return this.fetchAllPages(query, { search: searchTerm }, 'projects', {
750
+ maxItems: first,
751
+ pageSize: this.config.maxPageSize,
752
+ userConfig,
753
+ });
754
+ }
755
+ return this.query(query, { search: searchTerm, first: Math.min(first, this.config.maxPageSize), after }, userConfig);
673
756
  }
674
757
  getTypeName(t) {
675
758
  if (!t)
@@ -683,7 +766,7 @@ export class GitLabGraphQLClient {
683
766
  const values = (enumType && typeof enumType.getValues === 'function') ? enumType.getValues() : [];
684
767
  return Array.isArray(values) ? values.map((v) => v.name) : [];
685
768
  }
686
- async searchIssues(searchTerm, projectPath, state, first = 20, after, userConfig, assigneeUsernames, authorUsername, labelNames) {
769
+ async searchIssues(searchTerm, projectPath, state, first = 20, after, fetchAll = false, userConfig, assigneeUsernames, authorUsername, labelNames, sort) {
687
770
  await this.introspectSchema(userConfig);
688
771
  const mappedState = state && state.toLowerCase() !== 'all' ? state.toUpperCase() : undefined;
689
772
  if (projectPath) {
@@ -697,9 +780,9 @@ export class GitLabGraphQLClient {
697
780
  ? (allowed.find(v => v.toLowerCase() === state.toLowerCase()) || undefined)
698
781
  : undefined;
699
782
  const query = gql `
700
- query searchIssuesProject($projectPath: ID!, $search: String, $state: ${stateEnum}, $first: Int!, $after: String, $assigneeUsernames: [String!], $authorUsername: String, $labelName: [String!]) {
783
+ query searchIssuesProject($projectPath: ID!, $search: String, $state: ${stateEnum}, $first: Int!, $after: String, $assigneeUsernames: [String!], $authorUsername: String, $labelName: [String!], $sort: IssueSort) {
701
784
  project(fullPath: $projectPath) {
702
- issues(search: $search, state: $state, first: $first, after: $after, assigneeUsernames: $assigneeUsernames, authorUsername: $authorUsername, labelName: $labelName) {
785
+ issues(search: $search, state: $state, first: $first, after: $after, assigneeUsernames: $assigneeUsernames, authorUsername: $authorUsername, labelName: $labelName, sort: $sort) {
703
786
  pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
704
787
  nodes {
705
788
  id
@@ -716,6 +799,21 @@ export class GitLabGraphQLClient {
716
799
  }
717
800
  }
718
801
  `;
802
+ if (fetchAll) {
803
+ return this.fetchAllPages(query, {
804
+ projectPath,
805
+ search: searchTerm,
806
+ state: mapped,
807
+ assigneeUsernames,
808
+ authorUsername,
809
+ labelName: labelNames,
810
+ sort: sort || 'UPDATED_DESC'
811
+ }, 'project.issues', {
812
+ maxItems: first,
813
+ pageSize: this.config.maxPageSize,
814
+ userConfig,
815
+ });
816
+ }
719
817
  return this.query(query, {
720
818
  projectPath,
721
819
  search: searchTerm,
@@ -724,7 +822,8 @@ export class GitLabGraphQLClient {
724
822
  after,
725
823
  assigneeUsernames,
726
824
  authorUsername,
727
- labelName: labelNames
825
+ labelName: labelNames,
826
+ sort: sort || 'UPDATED_DESC'
728
827
  }, userConfig);
729
828
  }
730
829
  else {
@@ -742,8 +841,8 @@ export class GitLabGraphQLClient {
742
841
  // We keep description for AI context but limit nested collections (assignees/labels)
743
842
  // Note: Global Issue type doesn't have 'project' field - only project-scoped queries do
744
843
  const query = gql `
745
- query searchIssuesGlobal($search: String, $state: ${stateEnum}, $first: Int!, $after: String, $assigneeUsernames: [String!], $authorUsername: String, $labelName: [String!]) {
746
- issues(search: $search, state: $state, first: $first, after: $after, assigneeUsernames: $assigneeUsernames, authorUsername: $authorUsername, labelName: $labelName) {
844
+ query searchIssuesGlobal($search: String, $state: ${stateEnum}, $first: Int!, $after: String, $assigneeUsernames: [String!], $authorUsername: String, $labelName: [String!], $sort: IssueSort) {
845
+ issues(search: $search, state: $state, first: $first, after: $after, assigneeUsernames: $assigneeUsernames, authorUsername: $authorUsername, labelName: $labelName, sort: $sort) {
747
846
  pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
748
847
  nodes {
749
848
  id
@@ -762,6 +861,20 @@ export class GitLabGraphQLClient {
762
861
  `;
763
862
  // Note: Global search returns streamlined fields (no assignees/labels/project) for performance.
764
863
  // For full details and project attribution, search within a specific project using projectPath.
864
+ if (fetchAll) {
865
+ return this.fetchAllPages(query, {
866
+ search: searchTerm,
867
+ state: mapped,
868
+ assigneeUsernames,
869
+ authorUsername,
870
+ labelName: labelNames,
871
+ sort: sort || 'UPDATED_DESC'
872
+ }, 'issues', {
873
+ maxItems: first,
874
+ pageSize: this.config.maxPageSize,
875
+ userConfig,
876
+ });
877
+ }
765
878
  return this.query(query, {
766
879
  search: searchTerm,
767
880
  state: mapped,
@@ -769,17 +882,18 @@ export class GitLabGraphQLClient {
769
882
  after,
770
883
  assigneeUsernames,
771
884
  authorUsername,
772
- labelName: labelNames
885
+ labelName: labelNames,
886
+ sort: sort || 'UPDATED_DESC'
773
887
  }, userConfig);
774
888
  }
775
889
  }
776
- async searchMergeRequests(searchTerm, projectPath, state, first = 20, after, userConfig) {
890
+ async searchMergeRequests(searchTerm, projectPath, state, first = 20, after, fetchAll = false, userConfig, sort) {
777
891
  const mappedState = state && state.toLowerCase() !== 'all' ? state.toUpperCase() : undefined;
778
892
  if (projectPath) {
779
893
  const query = gql `
780
- query searchMergeRequestsProject($projectPath: ID!, $search: String, $state: MergeRequestState, $first: Int!, $after: String) {
894
+ query searchMergeRequestsProject($projectPath: ID!, $search: String, $state: MergeRequestState, $first: Int!, $after: String, $sort: MergeRequestSort) {
781
895
  project(fullPath: $projectPath) {
782
- mergeRequests(search: $search, state: $state, first: $first, after: $after) {
896
+ mergeRequests(search: $search, state: $state, first: $first, after: $after, sort: $sort) {
783
897
  pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
784
898
  nodes {
785
899
  id
@@ -798,12 +912,25 @@ export class GitLabGraphQLClient {
798
912
  }
799
913
  }
800
914
  `;
915
+ if (fetchAll) {
916
+ return this.fetchAllPages(query, {
917
+ projectPath,
918
+ search: searchTerm,
919
+ state: mappedState,
920
+ sort: sort || 'UPDATED_DESC'
921
+ }, 'project.mergeRequests', {
922
+ maxItems: first,
923
+ pageSize: this.config.maxPageSize,
924
+ userConfig,
925
+ });
926
+ }
801
927
  return this.query(query, {
802
928
  projectPath,
803
929
  search: searchTerm,
804
930
  state: mappedState,
805
- first: Math.min(first, 50),
806
- after
931
+ first: Math.min(first, this.config.maxPageSize),
932
+ after,
933
+ sort: sort || 'UPDATED_DESC'
807
934
  }, userConfig);
808
935
  }
809
936
  else {
@@ -851,9 +978,20 @@ export class GitLabGraphQLClient {
851
978
  }
852
979
  `;
853
980
  try {
981
+ if (fetchAll) {
982
+ const connectionPath = `user.${fieldName}`;
983
+ return this.fetchAllPages(query, {
984
+ username,
985
+ state: mappedState
986
+ }, connectionPath, {
987
+ maxItems: first,
988
+ pageSize: this.config.maxPageSize,
989
+ userConfig,
990
+ });
991
+ }
854
992
  const result = await this.query(query, {
855
993
  username,
856
- first: Math.min(first, 50),
994
+ first: Math.min(first, this.config.maxPageSize),
857
995
  after,
858
996
  state: mappedState
859
997
  }, userConfig);
@@ -997,10 +1135,16 @@ export class GitLabGraphQLClient {
997
1135
  ref: ref || "HEAD"
998
1136
  }, userConfig);
999
1137
  }
1000
- async searchUsers(searchTerm, first = 20, userConfig) {
1138
+ async searchUsers(searchTerm, first = 20, after, fetchAll = false, userConfig) {
1001
1139
  const query = gql `
1002
- query searchUsers($search: String!, $first: Int!) {
1003
- users(search: $search, first: $first) {
1140
+ query searchUsers($search: String!, $first: Int!, $after: String) {
1141
+ users(search: $search, first: $first, after: $after) {
1142
+ pageInfo {
1143
+ hasNextPage
1144
+ hasPreviousPage
1145
+ startCursor
1146
+ endCursor
1147
+ }
1004
1148
  nodes {
1005
1149
  id
1006
1150
  username
@@ -1012,12 +1156,25 @@ export class GitLabGraphQLClient {
1012
1156
  }
1013
1157
  }
1014
1158
  `;
1015
- return this.query(query, { search: searchTerm, first: Math.min(first, 50) }, userConfig);
1159
+ if (fetchAll) {
1160
+ return this.fetchAllPages(query, { search: searchTerm }, 'users', {
1161
+ maxItems: first,
1162
+ pageSize: this.config.maxPageSize,
1163
+ userConfig,
1164
+ });
1165
+ }
1166
+ return this.query(query, { search: searchTerm, first: Math.min(first, this.config.maxPageSize), after }, userConfig);
1016
1167
  }
1017
- async searchGroups(searchTerm, first = 20, userConfig) {
1168
+ async searchGroups(searchTerm, first = 20, after, fetchAll = false, userConfig) {
1018
1169
  const query = gql `
1019
- query searchGroups($search: String!, $first: Int!) {
1020
- groups(search: $search, first: $first) {
1170
+ query searchGroups($search: String!, $first: Int!, $after: String) {
1171
+ groups(search: $search, first: $first, after: $after) {
1172
+ pageInfo {
1173
+ hasNextPage
1174
+ hasPreviousPage
1175
+ startCursor
1176
+ endCursor
1177
+ }
1021
1178
  nodes {
1022
1179
  id
1023
1180
  name
@@ -1030,7 +1187,811 @@ export class GitLabGraphQLClient {
1030
1187
  }
1031
1188
  }
1032
1189
  `;
1033
- return this.query(query, { search: searchTerm, first: Math.min(first, 50) }, userConfig);
1190
+ if (fetchAll) {
1191
+ return this.fetchAllPages(query, { search: searchTerm }, 'groups', {
1192
+ maxItems: first,
1193
+ pageSize: this.config.maxPageSize,
1194
+ userConfig,
1195
+ });
1196
+ }
1197
+ return this.query(query, { search: searchTerm, first: Math.min(first, this.config.maxPageSize), after }, userConfig);
1198
+ }
1199
+ // ── CI/CD Pipelines ──────────────────────────────────────────────────
1200
+ async getMergeRequestPipelines(projectPath, iid, first = 20, after, fetchAll = false, userConfig) {
1201
+ const query = gql `
1202
+ query getMergeRequestPipelines($projectPath: ID!, $iid: String!, $first: Int!, $after: String) {
1203
+ project(fullPath: $projectPath) {
1204
+ mergeRequest(iid: $iid) {
1205
+ pipelines(first: $first, after: $after) {
1206
+ pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
1207
+ nodes {
1208
+ id
1209
+ iid
1210
+ status
1211
+ duration
1212
+ createdAt
1213
+ finishedAt
1214
+ ref
1215
+ sha
1216
+ detailedStatus {
1217
+ text
1218
+ label
1219
+ icon
1220
+ group
1221
+ detailsPath
1222
+ }
1223
+ stages {
1224
+ nodes {
1225
+ name
1226
+ status
1227
+ detailedStatus {
1228
+ text
1229
+ label
1230
+ }
1231
+ }
1232
+ }
1233
+ }
1234
+ }
1235
+ }
1236
+ }
1237
+ }
1238
+ `;
1239
+ if (fetchAll) {
1240
+ return this.fetchAllPages(query, { projectPath, iid }, 'project.mergeRequest.pipelines', {
1241
+ maxItems: first,
1242
+ pageSize: this.config.maxPageSize,
1243
+ userConfig,
1244
+ });
1245
+ }
1246
+ return this.query(query, { projectPath, iid, first: Math.min(first, this.config.maxPageSize), after }, userConfig);
1247
+ }
1248
+ async getPipelineJobs(projectPath, pipelineIid, first = 20, after, fetchAll = false, userConfig) {
1249
+ const query = gql `
1250
+ query getPipelineJobs($projectPath: ID!, $pipelineIid: ID!, $first: Int!, $after: String) {
1251
+ project(fullPath: $projectPath) {
1252
+ pipeline(iid: $pipelineIid) {
1253
+ id
1254
+ iid
1255
+ status
1256
+ jobs(first: $first, after: $after) {
1257
+ pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
1258
+ nodes {
1259
+ id
1260
+ name
1261
+ status
1262
+ stage {
1263
+ name
1264
+ }
1265
+ duration
1266
+ queuedDuration
1267
+ createdAt
1268
+ startedAt
1269
+ finishedAt
1270
+ retryable
1271
+ cancelable
1272
+ webPath
1273
+ detailedStatus {
1274
+ text
1275
+ label
1276
+ icon
1277
+ group
1278
+ detailsPath
1279
+ }
1280
+ }
1281
+ }
1282
+ }
1283
+ }
1284
+ }
1285
+ `;
1286
+ if (fetchAll) {
1287
+ return this.fetchAllPages(query, { projectPath, pipelineIid }, 'project.pipeline.jobs', {
1288
+ maxItems: first,
1289
+ pageSize: this.config.maxPageSize,
1290
+ userConfig,
1291
+ });
1292
+ }
1293
+ return this.query(query, { projectPath, pipelineIid, first: Math.min(first, this.config.maxPageSize), after }, userConfig);
1294
+ }
1295
+ async getPipelineId(projectPath, pipelineIid, userConfig) {
1296
+ const query = gql `
1297
+ query pipelineId($projectPath: ID!, $pipelineIid: ID!) {
1298
+ project(fullPath: $projectPath) {
1299
+ pipeline(iid: $pipelineIid) { id }
1300
+ }
1301
+ }
1302
+ `;
1303
+ const result = await this.query(query, { projectPath, pipelineIid }, userConfig);
1304
+ const id = result?.project?.pipeline?.id;
1305
+ if (!id)
1306
+ throw new Error('Pipeline not found');
1307
+ return id;
1308
+ }
1309
+ async managePipeline(projectPath, pipelineIid, action, userConfig) {
1310
+ await this.introspectSchema(userConfig);
1311
+ const mutationType = this.schema?.getMutationType();
1312
+ const fields = mutationType ? mutationType.getFields() : {};
1313
+ const pipelineGlobalId = await this.getPipelineId(projectPath, pipelineIid, userConfig);
1314
+ if (action === 'retry') {
1315
+ const fieldName = fields['pipelineRetry'] ? 'pipelineRetry' : null;
1316
+ if (!fieldName) {
1317
+ throw new Error('pipelineRetry mutation is not available on this GitLab instance');
1318
+ }
1319
+ const mutation = gql `
1320
+ mutation retryPipeline($input: PipelineRetryInput!) {
1321
+ pipelineRetry(input: $input) {
1322
+ pipeline { id iid status }
1323
+ errors
1324
+ }
1325
+ }
1326
+ `;
1327
+ const result = await this.query(mutation, { input: { id: pipelineGlobalId } }, userConfig, true);
1328
+ const payload = result.pipelineRetry;
1329
+ if (payload.errors && payload.errors.length > 0) {
1330
+ throw new Error(`Failed to retry pipeline: ${payload.errors.join(', ')}`);
1331
+ }
1332
+ return payload;
1333
+ }
1334
+ else {
1335
+ const fieldName = fields['pipelineCancel'] ? 'pipelineCancel' : null;
1336
+ if (!fieldName) {
1337
+ throw new Error('pipelineCancel mutation is not available on this GitLab instance');
1338
+ }
1339
+ const mutation = gql `
1340
+ mutation cancelPipeline($input: PipelineCancelInput!) {
1341
+ pipelineCancel(input: $input) {
1342
+ errors
1343
+ }
1344
+ }
1345
+ `;
1346
+ const result = await this.query(mutation, { input: { id: pipelineGlobalId } }, userConfig, true);
1347
+ const payload = result.pipelineCancel;
1348
+ if (payload.errors && payload.errors.length > 0) {
1349
+ throw new Error(`Failed to cancel pipeline: ${payload.errors.join(', ')}`);
1350
+ }
1351
+ return payload;
1352
+ }
1353
+ }
1354
+ // ── MR Diffs & Commits ───────────────────────────────────────────────
1355
+ async getMergeRequestDiffs(projectPath, iid, userConfig) {
1356
+ const query = gql `
1357
+ query getMergeRequestDiffs($projectPath: ID!, $iid: String!) {
1358
+ project(fullPath: $projectPath) {
1359
+ mergeRequest(iid: $iid) {
1360
+ diffStatsSummary {
1361
+ additions
1362
+ deletions
1363
+ fileCount
1364
+ }
1365
+ diffStats {
1366
+ path
1367
+ additions
1368
+ deletions
1369
+ }
1370
+ diffRefs {
1371
+ baseSha
1372
+ headSha
1373
+ startSha
1374
+ }
1375
+ }
1376
+ }
1377
+ }
1378
+ `;
1379
+ return this.query(query, { projectPath, iid }, userConfig);
1380
+ }
1381
+ async getMergeRequestCommits(projectPath, iid, first = 20, after, fetchAll = false, userConfig) {
1382
+ const query = gql `
1383
+ query getMergeRequestCommits($projectPath: ID!, $iid: String!, $first: Int!, $after: String) {
1384
+ project(fullPath: $projectPath) {
1385
+ mergeRequest(iid: $iid) {
1386
+ commitCount
1387
+ commitsWithoutMergeCommits(first: $first, after: $after) {
1388
+ pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
1389
+ nodes {
1390
+ sha
1391
+ shortId
1392
+ title
1393
+ message
1394
+ authorName
1395
+ authoredDate
1396
+ webUrl
1397
+ }
1398
+ }
1399
+ }
1400
+ }
1401
+ }
1402
+ `;
1403
+ if (fetchAll) {
1404
+ return this.fetchAllPages(query, { projectPath, iid }, 'project.mergeRequest.commitsWithoutMergeCommits', {
1405
+ maxItems: first,
1406
+ pageSize: this.config.maxPageSize,
1407
+ userConfig,
1408
+ });
1409
+ }
1410
+ return this.query(query, { projectPath, iid, first: Math.min(first, this.config.maxPageSize), after }, userConfig);
1411
+ }
1412
+ // ── Work Item Notes ──────────────────────────────────────────────────
1413
+ async getNotes(projectPath, noteableType, iid, first = 20, after, fetchAll = false, userConfig) {
1414
+ if (noteableType === 'issue') {
1415
+ const query = gql `
1416
+ query getIssueNotes($projectPath: ID!, $iid: String!, $first: Int!, $after: String) {
1417
+ project(fullPath: $projectPath) {
1418
+ issue(iid: $iid) {
1419
+ notes(first: $first, after: $after) {
1420
+ pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
1421
+ nodes {
1422
+ id
1423
+ body
1424
+ author { username name }
1425
+ createdAt
1426
+ updatedAt
1427
+ system
1428
+ internal
1429
+ url
1430
+ }
1431
+ }
1432
+ }
1433
+ }
1434
+ }
1435
+ `;
1436
+ if (fetchAll) {
1437
+ return this.fetchAllPages(query, { projectPath, iid }, 'project.issue.notes', {
1438
+ maxItems: first,
1439
+ pageSize: this.config.maxPageSize,
1440
+ userConfig,
1441
+ });
1442
+ }
1443
+ return this.query(query, { projectPath, iid, first: Math.min(first, this.config.maxPageSize), after }, userConfig);
1444
+ }
1445
+ else {
1446
+ const query = gql `
1447
+ query getMergeRequestNotes($projectPath: ID!, $iid: String!, $first: Int!, $after: String) {
1448
+ project(fullPath: $projectPath) {
1449
+ mergeRequest(iid: $iid) {
1450
+ notes(first: $first, after: $after) {
1451
+ pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
1452
+ nodes {
1453
+ id
1454
+ body
1455
+ author { username name }
1456
+ createdAt
1457
+ updatedAt
1458
+ system
1459
+ internal
1460
+ url
1461
+ position {
1462
+ filePath
1463
+ newLine
1464
+ oldLine
1465
+ }
1466
+ }
1467
+ }
1468
+ }
1469
+ }
1470
+ }
1471
+ `;
1472
+ if (fetchAll) {
1473
+ return this.fetchAllPages(query, { projectPath, iid }, 'project.mergeRequest.notes', {
1474
+ maxItems: first,
1475
+ pageSize: this.config.maxPageSize,
1476
+ userConfig,
1477
+ });
1478
+ }
1479
+ return this.query(query, { projectPath, iid, first: Math.min(first, this.config.maxPageSize), after }, userConfig);
1480
+ }
1481
+ }
1482
+ async createNote(projectPath, noteableType, iid, body, internal = false, userConfig) {
1483
+ await this.introspectSchema(userConfig);
1484
+ const mutationType = this.schema?.getMutationType();
1485
+ const fields = mutationType ? mutationType.getFields() : {};
1486
+ const fieldName = fields['createNote'] ? 'createNote' : null;
1487
+ if (!fieldName) {
1488
+ throw new Error('createNote mutation is not available on this GitLab instance');
1489
+ }
1490
+ // Resolve the noteable IID to a global ID
1491
+ const noteableId = noteableType === 'issue'
1492
+ ? await this.getIssueId(projectPath, iid, userConfig)
1493
+ : await this.getMergeRequestId(projectPath, iid, userConfig);
1494
+ const mutation = gql `
1495
+ mutation createNote($input: CreateNoteInput!) {
1496
+ createNote(input: $input) {
1497
+ note {
1498
+ id
1499
+ body
1500
+ author { username name }
1501
+ createdAt
1502
+ url
1503
+ internal
1504
+ }
1505
+ errors
1506
+ }
1507
+ }
1508
+ `;
1509
+ const input = {
1510
+ noteableId,
1511
+ body,
1512
+ };
1513
+ if (internal) {
1514
+ input.internal = true;
1515
+ }
1516
+ const result = await this.query(mutation, { input }, userConfig, true);
1517
+ return result.createNote;
1518
+ }
1519
+ // ── Milestones ─────────────────────────────────────────────────────
1520
+ async listMilestones(fullPath, isProject, state, search, includeAncestors = false, first = 20, after, fetchAll = false, userConfig) {
1521
+ const milestoneFields = `
1522
+ pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
1523
+ nodes {
1524
+ id
1525
+ iid
1526
+ title
1527
+ description
1528
+ state
1529
+ dueDate
1530
+ startDate
1531
+ expired
1532
+ upcoming
1533
+ webPath
1534
+ stats {
1535
+ totalIssuesCount
1536
+ closedIssuesCount
1537
+ }
1538
+ createdAt
1539
+ updatedAt
1540
+ }
1541
+ `;
1542
+ if (isProject) {
1543
+ const query = gql `
1544
+ query listProjectMilestones($fullPath: ID!, $state: MilestoneStateEnum, $searchTitle: String, $includeAncestors: Boolean, $first: Int!, $after: String) {
1545
+ project(fullPath: $fullPath) {
1546
+ milestones(state: $state, searchTitle: $searchTitle, includeAncestors: $includeAncestors, first: $first, after: $after) {
1547
+ ${milestoneFields}
1548
+ }
1549
+ }
1550
+ }
1551
+ `;
1552
+ if (fetchAll) {
1553
+ return this.fetchAllPages(query, {
1554
+ fullPath,
1555
+ state: state?.toLowerCase(),
1556
+ searchTitle: search,
1557
+ includeAncestors,
1558
+ }, 'project.milestones', {
1559
+ maxItems: first,
1560
+ pageSize: this.config.maxPageSize,
1561
+ userConfig,
1562
+ });
1563
+ }
1564
+ return this.query(query, {
1565
+ fullPath,
1566
+ state: state?.toLowerCase(),
1567
+ searchTitle: search,
1568
+ includeAncestors,
1569
+ first: Math.min(first, this.config.maxPageSize),
1570
+ after,
1571
+ }, userConfig);
1572
+ }
1573
+ else {
1574
+ const query = gql `
1575
+ query listGroupMilestones($fullPath: ID!, $state: MilestoneStateEnum, $searchTitle: String, $includeAncestors: Boolean, $first: Int!, $after: String) {
1576
+ group(fullPath: $fullPath) {
1577
+ milestones(state: $state, searchTitle: $searchTitle, includeAncestors: $includeAncestors, first: $first, after: $after) {
1578
+ ${milestoneFields}
1579
+ }
1580
+ }
1581
+ }
1582
+ `;
1583
+ if (fetchAll) {
1584
+ return this.fetchAllPages(query, {
1585
+ fullPath,
1586
+ state: state?.toLowerCase(),
1587
+ searchTitle: search,
1588
+ includeAncestors,
1589
+ }, 'group.milestones', {
1590
+ maxItems: first,
1591
+ pageSize: this.config.maxPageSize,
1592
+ userConfig,
1593
+ });
1594
+ }
1595
+ return this.query(query, {
1596
+ fullPath,
1597
+ state: state?.toLowerCase(),
1598
+ searchTitle: search,
1599
+ includeAncestors,
1600
+ first: Math.min(first, this.config.maxPageSize),
1601
+ after,
1602
+ }, userConfig);
1603
+ }
1604
+ }
1605
+ // ── Iterations ────────────────────────────────────────────────────
1606
+ async listIterations(groupPath, state, first = 20, after, fetchAll = false, userConfig) {
1607
+ const query = gql `
1608
+ query listIterations($groupPath: ID!, $state: IterationState, $first: Int!, $after: String) {
1609
+ group(fullPath: $groupPath) {
1610
+ iterations(state: $state, first: $first, after: $after) {
1611
+ pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
1612
+ nodes {
1613
+ id
1614
+ iid
1615
+ title
1616
+ description
1617
+ state
1618
+ startDate
1619
+ dueDate
1620
+ webUrl
1621
+ scopedPath
1622
+ iterationCadence {
1623
+ id
1624
+ title
1625
+ active
1626
+ durationInWeeks
1627
+ }
1628
+ createdAt
1629
+ updatedAt
1630
+ }
1631
+ }
1632
+ }
1633
+ }
1634
+ `;
1635
+ if (fetchAll) {
1636
+ return this.fetchAllPages(query, {
1637
+ groupPath,
1638
+ state: state?.toLowerCase(),
1639
+ }, 'group.iterations', {
1640
+ maxItems: first,
1641
+ pageSize: this.config.maxPageSize,
1642
+ userConfig,
1643
+ });
1644
+ }
1645
+ return this.query(query, {
1646
+ groupPath,
1647
+ state: state?.toLowerCase(),
1648
+ first: Math.min(first, this.config.maxPageSize),
1649
+ after,
1650
+ }, userConfig);
1651
+ }
1652
+ // ── Time Tracking ─────────────────────────────────────────────────
1653
+ async getTimeTracking(projectPath, resourceType, iid, includeTimelogs = true, first = 20, after, userConfig) {
1654
+ const timelogFields = includeTimelogs ? `
1655
+ timelogs(first: $first, after: $after) {
1656
+ pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
1657
+ nodes {
1658
+ id
1659
+ timeSpent
1660
+ summary
1661
+ spentAt
1662
+ user {
1663
+ username
1664
+ name
1665
+ }
1666
+ }
1667
+ }
1668
+ ` : '';
1669
+ if (resourceType === 'issue') {
1670
+ const query = gql `
1671
+ query getIssueTimeTracking($projectPath: ID!, $iid: String!, $first: Int!, $after: String) {
1672
+ project(fullPath: $projectPath) {
1673
+ issue(iid: $iid) {
1674
+ iid
1675
+ title
1676
+ webUrl
1677
+ timeEstimate
1678
+ totalTimeSpent
1679
+ humanTimeEstimate
1680
+ humanTotalTimeSpent
1681
+ ${timelogFields}
1682
+ }
1683
+ }
1684
+ }
1685
+ `;
1686
+ return this.query(query, { projectPath, iid, first: Math.min(first, this.config.maxPageSize), after }, userConfig);
1687
+ }
1688
+ else {
1689
+ const query = gql `
1690
+ query getMergeRequestTimeTracking($projectPath: ID!, $iid: String!, $first: Int!, $after: String) {
1691
+ project(fullPath: $projectPath) {
1692
+ mergeRequest(iid: $iid) {
1693
+ iid
1694
+ title
1695
+ webUrl
1696
+ timeEstimate
1697
+ totalTimeSpent
1698
+ humanTimeEstimate
1699
+ humanTotalTimeSpent
1700
+ ${timelogFields}
1701
+ }
1702
+ }
1703
+ }
1704
+ `;
1705
+ return this.query(query, { projectPath, iid, first: Math.min(first, this.config.maxPageSize), after }, userConfig);
1706
+ }
1707
+ }
1708
+ // ── MR Reviewers & Approvals ──────────────────────────────────────
1709
+ async getMergeRequestReviewers(projectPath, iid, userConfig) {
1710
+ const query = gql `
1711
+ query getMergeRequestReviewers($projectPath: ID!, $iid: String!) {
1712
+ project(fullPath: $projectPath) {
1713
+ mergeRequest(iid: $iid) {
1714
+ iid
1715
+ title
1716
+ webUrl
1717
+ state
1718
+ approved
1719
+ approvalsRequired
1720
+ approvalsLeft
1721
+ approvedBy {
1722
+ nodes {
1723
+ username
1724
+ name
1725
+ avatarUrl
1726
+ webUrl
1727
+ }
1728
+ }
1729
+ reviewers {
1730
+ nodes {
1731
+ username
1732
+ name
1733
+ avatarUrl
1734
+ webUrl
1735
+ mergeRequestInteraction {
1736
+ reviewState
1737
+ approved
1738
+ reviewed
1739
+ }
1740
+ }
1741
+ }
1742
+ }
1743
+ }
1744
+ }
1745
+ `;
1746
+ return this.query(query, { projectPath, iid }, userConfig);
1747
+ }
1748
+ // ── Project Statistics ────────────────────────────────────────────
1749
+ async getProjectStatistics(projectPath, userConfig) {
1750
+ const query = gql `
1751
+ query getProjectStatistics($projectPath: ID!) {
1752
+ project(fullPath: $projectPath) {
1753
+ id
1754
+ name
1755
+ fullPath
1756
+ webUrl
1757
+ starCount
1758
+ forksCount
1759
+ openIssuesCount
1760
+ statistics {
1761
+ repositorySize
1762
+ lfsObjectsSize
1763
+ buildArtifactsSize
1764
+ packagesSize
1765
+ wikiSize
1766
+ snippetsSize
1767
+ uploadsSize
1768
+ containerRegistrySize
1769
+ commitCount
1770
+ }
1771
+ openMergeRequests: mergeRequests(state: opened) {
1772
+ count
1773
+ }
1774
+ lastPipeline: pipelines(first: 1) {
1775
+ nodes {
1776
+ id
1777
+ iid
1778
+ status
1779
+ createdAt
1780
+ finishedAt
1781
+ ref
1782
+ detailedStatus {
1783
+ text
1784
+ label
1785
+ }
1786
+ }
1787
+ }
1788
+ releaseCount: releases {
1789
+ count
1790
+ }
1791
+ languages {
1792
+ name
1793
+ share
1794
+ color
1795
+ }
1796
+ }
1797
+ }
1798
+ `;
1799
+ return this.query(query, { projectPath }, userConfig);
1800
+ }
1801
+ // ── Group Members ─────────────────────────────────────────────────
1802
+ async listGroupMembers(groupPath, search, first = 20, after, fetchAll = false, userConfig) {
1803
+ const query = gql `
1804
+ query listGroupMembers($groupPath: ID!, $search: String, $first: Int!, $after: String) {
1805
+ group(fullPath: $groupPath) {
1806
+ groupMembers(search: $search, first: $first, after: $after) {
1807
+ pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
1808
+ nodes {
1809
+ id
1810
+ accessLevel {
1811
+ stringValue
1812
+ integerValue
1813
+ }
1814
+ createdAt
1815
+ expiresAt
1816
+ user {
1817
+ username
1818
+ name
1819
+ avatarUrl
1820
+ webUrl
1821
+ state
1822
+ }
1823
+ }
1824
+ }
1825
+ }
1826
+ }
1827
+ `;
1828
+ if (fetchAll) {
1829
+ return this.fetchAllPages(query, {
1830
+ groupPath,
1831
+ search: search || undefined,
1832
+ }, 'group.groupMembers', {
1833
+ maxItems: first,
1834
+ pageSize: this.config.maxPageSize,
1835
+ userConfig,
1836
+ });
1837
+ }
1838
+ return this.query(query, {
1839
+ groupPath,
1840
+ search: search || undefined,
1841
+ first: Math.min(first, this.config.maxPageSize),
1842
+ after,
1843
+ }, userConfig);
1844
+ }
1845
+ // ── Label Search ─────────────────────────────────────────────────────
1846
+ async searchLabels(fullPath, isProject, search, first = 20, after, fetchAll = false, userConfig) {
1847
+ if (isProject) {
1848
+ const query = gql `
1849
+ query searchProjectLabels($fullPath: ID!, $search: String, $first: Int!, $after: String) {
1850
+ project(fullPath: $fullPath) {
1851
+ labels(searchTerm: $search, first: $first, after: $after) {
1852
+ pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
1853
+ nodes {
1854
+ id
1855
+ title
1856
+ description
1857
+ color
1858
+ textColor
1859
+ createdAt
1860
+ updatedAt
1861
+ }
1862
+ }
1863
+ }
1864
+ }
1865
+ `;
1866
+ if (fetchAll) {
1867
+ return this.fetchAllPages(query, { fullPath, search }, 'project.labels', {
1868
+ maxItems: first,
1869
+ pageSize: this.config.maxPageSize,
1870
+ userConfig,
1871
+ });
1872
+ }
1873
+ return this.query(query, { fullPath, search, first: Math.min(first, this.config.maxPageSize), after }, userConfig);
1874
+ }
1875
+ else {
1876
+ const query = gql `
1877
+ query searchGroupLabels($fullPath: ID!, $search: String, $first: Int!, $after: String) {
1878
+ group(fullPath: $fullPath) {
1879
+ labels(searchTerm: $search, first: $first, after: $after) {
1880
+ pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
1881
+ nodes {
1882
+ id
1883
+ title
1884
+ description
1885
+ color
1886
+ textColor
1887
+ createdAt
1888
+ updatedAt
1889
+ }
1890
+ }
1891
+ }
1892
+ }
1893
+ `;
1894
+ if (fetchAll) {
1895
+ return this.fetchAllPages(query, { fullPath, search }, 'group.labels', {
1896
+ maxItems: first,
1897
+ pageSize: this.config.maxPageSize,
1898
+ userConfig,
1899
+ });
1900
+ }
1901
+ return this.query(query, { fullPath, search, first: Math.min(first, this.config.maxPageSize), after }, userConfig);
1902
+ }
1903
+ }
1904
+ /**
1905
+ * Resolve the GitLab base URL and access token for a REST call, honoring
1906
+ * per-request user credentials and falling back to the shared token.
1907
+ */
1908
+ resolveRestAuth(userConfig, requiresWrite = false) {
1909
+ if (userConfig) {
1910
+ return {
1911
+ baseUrl: userConfig.gitlabUrl || this.config.gitlabUrl,
1912
+ token: userConfig.accessToken,
1913
+ };
1914
+ }
1915
+ if (requiresWrite) {
1916
+ throw new Error('Write operations require user authentication. Please provide your GitLab credentials.');
1917
+ }
1918
+ if (this.config.sharedAccessToken && this.config.authMode !== 'per-user') {
1919
+ return { baseUrl: this.config.gitlabUrl, token: this.config.sharedAccessToken };
1920
+ }
1921
+ throw new Error('This operation requires user authentication. Please provide your GitLab credentials.');
1922
+ }
1923
+ /**
1924
+ * Perform a GitLab REST API v4 request. Used for endpoints not exposed via GraphQL
1925
+ * (e.g., broadcast messages).
1926
+ */
1927
+ async restRequest(method, path, options = {}) {
1928
+ const { baseUrl, token } = this.resolveRestAuth(options.userConfig, options.requiresWrite);
1929
+ const url = new URL(`${baseUrl.replace(/\/$/, '')}/api/v4${path}`);
1930
+ if (options.query) {
1931
+ for (const [k, v] of Object.entries(options.query)) {
1932
+ if (v !== undefined && v !== null)
1933
+ url.searchParams.set(k, String(v));
1934
+ }
1935
+ }
1936
+ const timeoutMs = this.config.defaultTimeout || 30000;
1937
+ return this.executeWithRetry(async () => {
1938
+ const controller = new AbortController();
1939
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
1940
+ try {
1941
+ const response = await fetch(url.toString(), {
1942
+ method,
1943
+ headers: {
1944
+ Authorization: `Bearer ${token}`,
1945
+ 'Content-Type': 'application/json',
1946
+ Accept: 'application/json',
1947
+ },
1948
+ body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
1949
+ signal: controller.signal,
1950
+ });
1951
+ if (!response.ok) {
1952
+ const statusCode = response.status;
1953
+ let message = `${response.statusText}`;
1954
+ try {
1955
+ const errBody = await response.json();
1956
+ message = errBody?.message || errBody?.error || JSON.stringify(errBody);
1957
+ }
1958
+ catch {
1959
+ // non-JSON body
1960
+ }
1961
+ const retryable = RETRY_CONFIG.retryableStatusCodes.includes(statusCode);
1962
+ throw new GitLabAPIError(`GitLab REST ${method} ${path} failed (${statusCode}): ${message}`, {
1963
+ code: statusCode === 401 ? 'AUTH_FAILED' : statusCode === 403 ? 'FORBIDDEN' : 'HTTP_ERROR',
1964
+ statusCode,
1965
+ isRetryable: retryable,
1966
+ });
1967
+ }
1968
+ if (response.status === 204)
1969
+ return undefined;
1970
+ const text = await response.text();
1971
+ return (text ? JSON.parse(text) : undefined);
1972
+ }
1973
+ finally {
1974
+ clearTimeout(timeoutId);
1975
+ }
1976
+ }, `REST ${method} ${path}`);
1977
+ }
1978
+ async listBroadcastMessages(page = 1, perPage = 20, userConfig) {
1979
+ return this.restRequest('GET', '/broadcast_messages', {
1980
+ query: { page, per_page: Math.min(perPage, this.config.maxPageSize) },
1981
+ userConfig,
1982
+ });
1983
+ }
1984
+ async getBroadcastMessage(id, userConfig) {
1985
+ return this.restRequest('GET', `/broadcast_messages/${id}`, { userConfig });
1986
+ }
1987
+ async createBroadcastMessage(input, userConfig) {
1988
+ return this.restRequest('POST', '/broadcast_messages', { body: input, userConfig, requiresWrite: true });
1989
+ }
1990
+ async updateBroadcastMessage(id, input, userConfig) {
1991
+ return this.restRequest('PUT', `/broadcast_messages/${id}`, { body: input, userConfig, requiresWrite: true });
1992
+ }
1993
+ async deleteBroadcastMessage(id, userConfig) {
1994
+ return this.restRequest('DELETE', `/broadcast_messages/${id}`, { userConfig, requiresWrite: true });
1034
1995
  }
1035
1996
  }
1036
1997
  //# sourceMappingURL=gitlab-client.js.map