@wanadev/mcp-gitlab 1.0.4 → 1.1.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.
package/dist/client.js CHANGED
@@ -1,27 +1,33 @@
1
+ import { toGid, Q_CURRENT_USER, Q_GROUPS, Q_EPICS, Q_EPIC, Q_EPIC_ISSUES, Q_EPIC_NOTES, Q_GROUP_ISSUES, Q_ISSUE, Q_ISSUE_NOTES, Q_MILESTONES, Q_MILESTONE, Q_MERGE_REQUESTS, Q_MERGE_REQUEST, Q_ITERATIONS, Q_PROJECTS, Q_MEMBERS, Q_LABELS, Q_BOARDS, Q_PROJECT_PATH, M_CREATE_EPIC, M_UPDATE_EPIC, M_CREATE_MILESTONE, M_UPDATE_MILESTONE, M_CREATE_ISSUE, M_UPDATE_ISSUE, M_CREATE_NOTE, M_EPIC_ADD_ISSUE, mapUser, mapEpic, mapIssue, mapMilestone, mapMergeRequest, mapGroup, mapProject, mapMember, mapLabel, mapNote, mapBoard, mapIteration, } from "./graphql.js";
1
2
  export class GitLabClient {
2
3
  baseUrl;
3
4
  token;
4
5
  readOnly;
6
+ projectPathCache = new Map();
5
7
  constructor(config) {
6
8
  this.baseUrl = config.baseUrl.replace(/\/+$/, "");
7
9
  this.token = config.token;
8
10
  this.readOnly = config.readOnly;
9
11
  }
10
- async request(method, path, body) {
11
- if (this.readOnly && method !== "GET") {
12
- throw new Error(`Mode lecture seule actif (GITLAB_READ_ONLY=true). Impossible d'effectuer une requete ${method}.`);
12
+ // ---------------------------------------------------------------------------
13
+ // Core GraphQL methods
14
+ // ---------------------------------------------------------------------------
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ async graphql(query, variables) {
17
+ if (this.readOnly && query.trimStart().startsWith("mutation")) {
18
+ throw new Error("Mode lecture seule actif (GITLAB_READ_ONLY=true). Impossible d'effectuer une mutation.");
13
19
  }
14
- const url = new URL(`/api/v4${path}`, this.baseUrl);
20
+ const url = `${this.baseUrl}/api/graphql`;
15
21
  let lastError = null;
16
22
  for (let attempt = 0; attempt < 3; attempt++) {
17
23
  try {
18
- const response = await fetch(url.toString(), {
19
- method,
24
+ const response = await fetch(url, {
25
+ method: "POST",
20
26
  headers: {
21
27
  "PRIVATE-TOKEN": this.token,
22
28
  "Content-Type": "application/json",
23
29
  },
24
- body: body ? JSON.stringify(body) : undefined,
30
+ body: JSON.stringify({ query, variables }),
25
31
  signal: AbortSignal.timeout(15_000),
26
32
  });
27
33
  if (response.status === 429) {
@@ -34,13 +40,15 @@ export class GitLabClient {
34
40
  }
35
41
  if (!response.ok) {
36
42
  const text = await response.text().catch(() => "");
37
- const message = this.formatHttpError(response.status, text, path);
38
- throw new Error(message);
43
+ throw new Error(this.formatHttpError(response.status, text));
39
44
  }
40
- if (response.status === 204) {
41
- return undefined;
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ const json = (await response.json());
47
+ if (json.errors?.length) {
48
+ const messages = json.errors.map((e) => e.message).join("; ");
49
+ throw new Error(`GraphQL error: ${messages}`);
42
50
  }
43
- return (await response.json());
51
+ return json.data;
44
52
  }
45
53
  catch (error) {
46
54
  if (error instanceof Error &&
@@ -48,241 +56,393 @@ export class GitLabClient {
48
56
  throw error;
49
57
  }
50
58
  lastError = error instanceof Error ? error : new Error(String(error));
51
- if (error instanceof DOMException &&
52
- error.name === "TimeoutError") {
59
+ if (error instanceof DOMException && error.name === "TimeoutError") {
53
60
  if (attempt < 2)
54
61
  continue;
55
62
  }
56
- else if (!(error instanceof Error && error.message.includes("429"))) {
63
+ else {
57
64
  throw lastError;
58
65
  }
59
66
  }
60
67
  }
61
68
  throw lastError ?? new Error("Echec apres 3 tentatives");
62
69
  }
63
- formatHttpError(status, body, path) {
64
- let detail = "";
65
- try {
66
- const parsed = JSON.parse(body);
67
- detail = parsed.message ?? parsed.error ?? body;
70
+ async graphqlPaginate(query, variables, extract, mapper, maxItems = 500) {
71
+ const results = [];
72
+ let after = null;
73
+ while (results.length < maxItems) {
74
+ const data = await this.graphql(query, { ...variables, after });
75
+ const connection = extract(data);
76
+ if (!connection || !connection.nodes)
77
+ break;
78
+ for (const node of connection.nodes) {
79
+ results.push(mapper(node));
80
+ }
81
+ if (!connection.pageInfo.hasNextPage || !connection.pageInfo.endCursor)
82
+ break;
83
+ after = connection.pageInfo.endCursor;
68
84
  }
69
- catch {
70
- detail = body;
85
+ return results.slice(0, maxItems);
86
+ }
87
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
+ async mutate(query, input, resultKey) {
89
+ const data = await this.graphql(query, { input });
90
+ const result = data[resultKey];
91
+ if (result?.errors?.length) {
92
+ throw new Error(`GitLab mutation error: ${result.errors.join("; ")}`);
71
93
  }
94
+ return result;
95
+ }
96
+ formatHttpError(status, body) {
72
97
  switch (status) {
73
98
  case 401:
74
99
  return "Authentification echouee. Verifiez votre GITLAB_TOKEN.";
75
100
  case 403:
76
- return `Acces refuse (403) sur ${path}. Verifiez les permissions du token et le niveau GitLab (Premium/Ultimate pour les epics).`;
101
+ return "Acces refuse (403). Verifiez les permissions du token et le niveau GitLab (Premium/Ultimate pour les epics).";
77
102
  case 404:
78
- return `Ressource introuvable (404) : ${path}. Verifiez l'ID du groupe/projet.`;
103
+ return "Endpoint GraphQL introuvable (404). Verifiez GITLAB_BASE_URL.";
79
104
  default:
80
- return `Erreur GitLab ${status} sur ${path}: ${detail}`;
81
- }
82
- }
83
- async paginate(path, params) {
84
- const results = [];
85
- let page = 1;
86
- const maxItems = 500;
87
- while (results.length < maxItems) {
88
- const url = new URL(`/api/v4${path}`, this.baseUrl);
89
- url.searchParams.set("per_page", "100");
90
- url.searchParams.set("page", String(page));
91
- if (params) {
92
- for (const [key, value] of Object.entries(params)) {
93
- if (value !== undefined && value !== "") {
94
- url.searchParams.set(key, value);
95
- }
96
- }
97
- }
98
- const response = await fetch(url.toString(), {
99
- method: "GET",
100
- headers: {
101
- "PRIVATE-TOKEN": this.token,
102
- "Content-Type": "application/json",
103
- },
104
- signal: AbortSignal.timeout(15_000),
105
- });
106
- if (!response.ok) {
107
- const text = await response.text().catch(() => "");
108
- throw new Error(this.formatHttpError(response.status, text, path));
109
- }
110
- const data = (await response.json());
111
- if (data.length === 0)
112
- break;
113
- results.push(...data);
114
- const totalPages = response.headers.get("X-Total-Pages");
115
- if (totalPages && page >= parseInt(totalPages, 10))
116
- break;
117
- page++;
105
+ return `Erreur GitLab ${status}: ${body.slice(0, 200)}`;
118
106
  }
119
- return results.slice(0, maxItems);
120
107
  }
121
- // --- Groups ---
108
+ async resolveProjectPath(projectId) {
109
+ const cached = this.projectPathCache.get(projectId);
110
+ if (cached)
111
+ return cached;
112
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
+ const data = await this.graphql(Q_PROJECT_PATH, { id: toGid("Project", projectId) });
114
+ const fullPath = data.project?.fullPath;
115
+ if (!fullPath)
116
+ throw new Error(`Project ${projectId} not found`);
117
+ this.projectPathCache.set(projectId, fullPath);
118
+ return fullPath;
119
+ }
120
+ // ---------------------------------------------------------------------------
121
+ // Groups
122
+ // ---------------------------------------------------------------------------
122
123
  async listGroups(params) {
123
- const queryParams = {};
124
- if (params?.search)
125
- queryParams["search"] = params.search;
126
- if (params?.top_level_only)
127
- queryParams["top_level_only"] = "true";
128
- return this.paginate("/groups", queryParams);
129
- }
130
- // --- Epics ---
124
+ return this.graphqlPaginate(Q_GROUPS, { search: params?.search ?? null },
125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
+ (d) => d.groups, mapGroup);
127
+ }
128
+ // ---------------------------------------------------------------------------
129
+ // Epics
130
+ // ---------------------------------------------------------------------------
131
131
  async listEpics(groupId, params) {
132
- const queryParams = {};
133
- if (params?.state)
134
- queryParams["state"] = params.state;
135
- if (params?.search)
136
- queryParams["search"] = params.search;
137
- if (params?.labels)
138
- queryParams["labels"] = params.labels;
139
- if (params?.order_by)
140
- queryParams["order_by"] = params.order_by;
141
- if (params?.sort)
142
- queryParams["sort"] = params.sort;
143
- return this.paginate(`/groups/${encodeURIComponent(groupId)}/epics`, queryParams);
132
+ return this.graphqlPaginate(Q_EPICS, {
133
+ fullPath: groupId,
134
+ state: params?.state && params.state !== "all" ? params.state : null,
135
+ search: params?.search ?? null,
136
+ labelName: params?.labels ? params.labels.split(",").map(s => s.trim()) : null,
137
+ },
138
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
139
+ (d) => d.group?.epics, mapEpic);
144
140
  }
145
141
  async getEpic(groupId, epicIid) {
146
- return this.request("GET", `/groups/${encodeURIComponent(groupId)}/epics/${epicIid}`);
142
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
143
+ const data = await this.graphql(Q_EPIC, { fullPath: groupId, iid: String(epicIid) });
144
+ if (!data.group?.epic)
145
+ throw new Error(`Epic #${epicIid} not found in group ${groupId}`);
146
+ return mapEpic(data.group.epic);
147
147
  }
148
148
  async createEpic(groupId, data) {
149
- return this.request("POST", `/groups/${encodeURIComponent(groupId)}/epics`, data);
149
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
150
+ const input = {
151
+ groupPath: groupId,
152
+ title: data.title,
153
+ };
154
+ if (data.description)
155
+ input.description = data.description;
156
+ if (data.labels)
157
+ input.addLabelIds = data.labels.split(",").map(s => s.trim());
158
+ if (data.milestone_id)
159
+ input.milestoneId = toGid("Milestone", data.milestone_id);
160
+ if (data.start_date)
161
+ input.startDateFixed = data.start_date;
162
+ if (data.due_date)
163
+ input.dueDateFixed = data.due_date;
164
+ const result = await this.mutate(M_CREATE_EPIC, input, "createEpic");
165
+ return mapEpic(result.epic);
150
166
  }
151
167
  async updateEpic(groupId, epicIid, data) {
152
- return this.request("PUT", `/groups/${encodeURIComponent(groupId)}/epics/${epicIid}`, data);
168
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
169
+ const input = {
170
+ groupPath: groupId,
171
+ iid: String(epicIid),
172
+ };
173
+ if (data.title)
174
+ input.title = data.title;
175
+ if (data.description)
176
+ input.description = data.description;
177
+ if (data.labels)
178
+ input.addLabelIds = data.labels.split(",").map(s => s.trim());
179
+ if (data.milestone_id)
180
+ input.milestoneId = toGid("Milestone", data.milestone_id);
181
+ if (data.start_date)
182
+ input.startDateFixed = data.start_date;
183
+ if (data.due_date)
184
+ input.dueDateFixed = data.due_date;
185
+ if (data.state_event === "close")
186
+ input.stateEvent = "CLOSE";
187
+ const result = await this.mutate(M_UPDATE_EPIC, input, "updateEpic");
188
+ return mapEpic(result.epic);
153
189
  }
154
190
  async closeEpic(groupId, epicIid) {
155
191
  return this.updateEpic(groupId, epicIid, { state_event: "close" });
156
192
  }
157
193
  async listEpicIssues(groupId, epicIid) {
158
- return this.paginate(`/groups/${encodeURIComponent(groupId)}/epics/${epicIid}/issues`);
194
+ return this.graphqlPaginate(Q_EPIC_ISSUES, { fullPath: groupId, epicIid: String(epicIid) },
195
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
196
+ (d) => d.group?.epic?.issues,
197
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
198
+ (n) => mapIssue(n, this.baseUrl));
159
199
  }
160
200
  async addIssueToEpic(groupId, epicIid, issueId) {
161
- return this.request("POST", `/groups/${encodeURIComponent(groupId)}/epics/${epicIid}/issues/${issueId}`);
201
+ const epic = await this.getEpic(groupId, epicIid);
202
+ const input = {
203
+ iid: String(epicIid),
204
+ groupPath: groupId,
205
+ issueIid: String(issueId),
206
+ };
207
+ await this.mutate(M_EPIC_ADD_ISSUE, input, "epicAddIssue");
208
+ return { id: 0, epic, issue: {} };
162
209
  }
163
210
  async listEpicNotes(groupId, epicIid) {
164
- return this.paginate(`/groups/${encodeURIComponent(groupId)}/epics/${epicIid}/notes`);
211
+ return this.graphqlPaginate(Q_EPIC_NOTES, { fullPath: groupId, epicIid: String(epicIid) },
212
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
213
+ (d) => d.group?.epic?.notes, mapNote);
165
214
  }
166
215
  async addEpicNote(groupId, epicIid, body) {
167
- return this.request("POST", `/groups/${encodeURIComponent(groupId)}/epics/${epicIid}/notes`, { body });
168
- }
169
- // --- Issues ---
216
+ const epic = await this.getEpic(groupId, epicIid);
217
+ const noteableId = toGid("Epic", epic.id);
218
+ const result = await this.mutate(M_CREATE_NOTE, { noteableId, body }, "createNote");
219
+ return mapNote(result.note);
220
+ }
221
+ // ---------------------------------------------------------------------------
222
+ // Issues
223
+ // ---------------------------------------------------------------------------
170
224
  async listGroupIssues(groupId, params) {
171
- const queryParams = {};
172
- if (params?.state)
173
- queryParams["state"] = params.state;
174
- if (params?.search)
175
- queryParams["search"] = params.search;
176
- if (params?.labels)
177
- queryParams["labels"] = params.labels;
178
- if (params?.milestone)
179
- queryParams["milestone"] = params.milestone;
180
- if (params?.assignee_username)
181
- queryParams["assignee_username"] = params.assignee_username;
182
- if (params?.order_by)
183
- queryParams["order_by"] = params.order_by;
184
- if (params?.sort)
185
- queryParams["sort"] = params.sort;
186
- return this.paginate(`/groups/${encodeURIComponent(groupId)}/issues`, queryParams);
225
+ return this.graphqlPaginate(Q_GROUP_ISSUES, {
226
+ fullPath: groupId,
227
+ state: params?.state && params.state !== "all" ? params.state : null,
228
+ search: params?.search ?? null,
229
+ labelName: params?.labels ? params.labels.split(",").map(s => s.trim()) : null,
230
+ milestoneTitle: params?.milestone ? [params.milestone] : null,
231
+ assigneeUsernames: params?.assignee_username ? [params.assignee_username] : null,
232
+ },
233
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
234
+ (d) => d.group?.issues,
235
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
236
+ (n) => mapIssue(n, this.baseUrl));
187
237
  }
188
238
  async getIssue(projectId, issueIid) {
189
- return this.request("GET", `/projects/${projectId}/issues/${issueIid}`);
239
+ const projectPath = await this.resolveProjectPath(projectId);
240
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
241
+ const data = await this.graphql(Q_ISSUE, { projectPath, iid: String(issueIid) });
242
+ if (!data.project?.issue)
243
+ throw new Error(`Issue #${issueIid} not found in project ${projectId}`);
244
+ return mapIssue(data.project.issue, this.baseUrl);
190
245
  }
191
246
  async createIssue(projectId, data) {
192
- return this.request("POST", `/projects/${projectId}/issues`, data);
247
+ const projectPath = await this.resolveProjectPath(projectId);
248
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
249
+ const input = {
250
+ projectPath,
251
+ title: data.title,
252
+ };
253
+ if (data.description)
254
+ input.description = data.description;
255
+ if (data.labels)
256
+ input.labels = data.labels.split(",").map(s => s.trim());
257
+ if (data.milestone_id)
258
+ input.milestoneId = toGid("Milestone", data.milestone_id);
259
+ if (data.assignee_ids)
260
+ input.assigneeIds = data.assignee_ids.map(id => toGid("User", id));
261
+ if (data.due_date)
262
+ input.dueDate = data.due_date;
263
+ if (data.weight != null)
264
+ input.weight = data.weight;
265
+ if (data.epic_id)
266
+ input.epicId = toGid("Epic", data.epic_id);
267
+ if (data.iteration_id)
268
+ input.iterationId = toGid("Iteration", data.iteration_id);
269
+ const result = await this.mutate(M_CREATE_ISSUE, input, "createIssue");
270
+ return mapIssue(result.issue, this.baseUrl);
193
271
  }
194
272
  async updateIssue(projectId, issueIid, data) {
195
- return this.request("PUT", `/projects/${projectId}/issues/${issueIid}`, data);
273
+ const projectPath = await this.resolveProjectPath(projectId);
274
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
275
+ const input = {
276
+ projectPath,
277
+ iid: String(issueIid),
278
+ };
279
+ if (data.title)
280
+ input.title = data.title;
281
+ if (data.description)
282
+ input.description = data.description;
283
+ if (data.labels)
284
+ input.labels = data.labels.split(",").map(s => s.trim());
285
+ if (data.milestone_id)
286
+ input.milestoneId = toGid("Milestone", data.milestone_id);
287
+ if (data.assignee_ids)
288
+ input.assigneeIds = data.assignee_ids.map(id => toGid("User", id));
289
+ if (data.due_date)
290
+ input.dueDate = data.due_date;
291
+ if (data.weight != null)
292
+ input.weight = data.weight;
293
+ if (data.state_event === "close")
294
+ input.stateEvent = "CLOSE";
295
+ if (data.iteration_id)
296
+ input.iterationId = toGid("Iteration", data.iteration_id);
297
+ const result = await this.mutate(M_UPDATE_ISSUE, input, "updateIssue");
298
+ return mapIssue(result.issue, this.baseUrl);
196
299
  }
197
300
  async closeIssue(projectId, issueIid) {
198
301
  return this.updateIssue(projectId, issueIid, { state_event: "close" });
199
302
  }
200
303
  async listIssueNotes(projectId, issueIid) {
201
- return this.paginate(`/projects/${projectId}/issues/${issueIid}/notes`);
304
+ const projectPath = await this.resolveProjectPath(projectId);
305
+ return this.graphqlPaginate(Q_ISSUE_NOTES, { projectPath, issueIid: String(issueIid) },
306
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
307
+ (d) => d.project?.issue?.notes, mapNote);
202
308
  }
203
309
  async addIssueNote(projectId, issueIid, body) {
204
- return this.request("POST", `/projects/${projectId}/issues/${issueIid}/notes`, { body });
205
- }
206
- // --- Merge Requests ---
310
+ const issue = await this.getIssue(projectId, issueIid);
311
+ const noteableId = toGid("Issue", issue.id);
312
+ const result = await this.mutate(M_CREATE_NOTE, { noteableId, body }, "createNote");
313
+ return mapNote(result.note);
314
+ }
315
+ // ---------------------------------------------------------------------------
316
+ // Merge Requests
317
+ // ---------------------------------------------------------------------------
207
318
  async listGroupMergeRequests(groupId, params) {
208
- const queryParams = {};
209
- if (params?.state)
210
- queryParams["state"] = params.state;
211
- if (params?.search)
212
- queryParams["search"] = params.search;
213
- if (params?.labels)
214
- queryParams["labels"] = params.labels;
215
- if (params?.milestone)
216
- queryParams["milestone"] = params.milestone;
217
- if (params?.author_username)
218
- queryParams["author_username"] = params.author_username;
219
- if (params?.reviewer_username)
220
- queryParams["reviewer_username"] = params.reviewer_username;
221
- if (params?.order_by)
222
- queryParams["order_by"] = params.order_by;
223
- if (params?.sort)
224
- queryParams["sort"] = params.sort;
225
- return this.paginate(`/groups/${encodeURIComponent(groupId)}/merge_requests`, queryParams);
319
+ return this.graphqlPaginate(Q_MERGE_REQUESTS, {
320
+ fullPath: groupId,
321
+ state: params?.state && params.state !== "all" ? params.state : null,
322
+ labels: params?.labels ? params.labels.split(",").map(s => s.trim()) : null,
323
+ milestoneTitle: params?.milestone ?? null,
324
+ authorUsername: params?.author_username ?? null,
325
+ reviewerUsername: params?.reviewer_username ?? null,
326
+ },
327
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
328
+ (d) => d.group?.mergeRequests,
329
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
330
+ (n) => mapMergeRequest(n, this.baseUrl));
226
331
  }
227
332
  async getMergeRequest(projectId, mrIid) {
228
- return this.request("GET", `/projects/${projectId}/merge_requests/${mrIid}`);
229
- }
230
- // --- Milestones ---
333
+ const projectPath = await this.resolveProjectPath(projectId);
334
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
335
+ const data = await this.graphql(Q_MERGE_REQUEST, { projectPath, iid: String(mrIid) });
336
+ if (!data.project?.mergeRequest)
337
+ throw new Error(`MR !${mrIid} not found in project ${projectId}`);
338
+ return mapMergeRequest(data.project.mergeRequest, this.baseUrl);
339
+ }
340
+ // ---------------------------------------------------------------------------
341
+ // Milestones
342
+ // ---------------------------------------------------------------------------
231
343
  async listGroupMilestones(groupId, params) {
232
- const queryParams = {};
233
- if (params?.state)
234
- queryParams["state"] = params.state;
235
- if (params?.search)
236
- queryParams["search"] = params.search;
237
- return this.paginate(`/groups/${encodeURIComponent(groupId)}/milestones`, queryParams);
344
+ return this.graphqlPaginate(Q_MILESTONES, {
345
+ fullPath: groupId,
346
+ state: params?.state ?? null,
347
+ searchTitle: params?.search ?? null,
348
+ },
349
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
350
+ (d) => d.group?.milestones,
351
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
352
+ (n) => mapMilestone(n, this.baseUrl));
238
353
  }
239
354
  async getMilestone(groupId, milestoneId) {
240
- return this.request("GET", `/groups/${encodeURIComponent(groupId)}/milestones/${milestoneId}`);
355
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
356
+ const data = await this.graphql(Q_MILESTONE, {
357
+ fullPath: groupId,
358
+ ids: [toGid("Milestone", milestoneId)],
359
+ });
360
+ const node = data.group?.milestones?.nodes?.[0];
361
+ if (!node)
362
+ throw new Error(`Milestone ${milestoneId} not found in group ${groupId}`);
363
+ return mapMilestone(node, this.baseUrl);
241
364
  }
242
365
  async createMilestone(groupId, data) {
243
- return this.request("POST", `/groups/${encodeURIComponent(groupId)}/milestones`, data);
366
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
367
+ const input = {
368
+ groupPath: groupId,
369
+ title: data.title,
370
+ };
371
+ if (data.description)
372
+ input.description = data.description;
373
+ if (data.start_date)
374
+ input.startDate = data.start_date;
375
+ if (data.due_date)
376
+ input.dueDate = data.due_date;
377
+ const result = await this.mutate(M_CREATE_MILESTONE, input, "createMilestone");
378
+ return mapMilestone(result.milestone, this.baseUrl);
244
379
  }
245
380
  async updateMilestone(groupId, milestoneId, data) {
246
- return this.request("PUT", `/groups/${encodeURIComponent(groupId)}/milestones/${milestoneId}`, data);
381
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
382
+ const input = {
383
+ id: toGid("Milestone", milestoneId),
384
+ };
385
+ if (data.title)
386
+ input.title = data.title;
387
+ if (data.description)
388
+ input.description = data.description;
389
+ if (data.start_date)
390
+ input.startDate = data.start_date;
391
+ if (data.due_date)
392
+ input.dueDate = data.due_date;
393
+ if (data.state_event === "close")
394
+ input.stateEvent = "CLOSE";
395
+ const result = await this.mutate(M_UPDATE_MILESTONE, input, "updateMilestone");
396
+ return mapMilestone(result.milestone, this.baseUrl);
247
397
  }
248
398
  async closeMilestone(groupId, milestoneId) {
249
399
  return this.updateMilestone(groupId, milestoneId, { state_event: "close" });
250
400
  }
251
- // --- Iterations ---
401
+ // ---------------------------------------------------------------------------
402
+ // Iterations
403
+ // ---------------------------------------------------------------------------
252
404
  async listGroupIterations(groupId, params) {
253
- const queryParams = {};
254
- if (params?.state)
255
- queryParams["state"] = params.state;
256
- if (params?.search)
257
- queryParams["search"] = params.search;
258
- return this.paginate(`/groups/${encodeURIComponent(groupId)}/iterations`, queryParams);
259
- }
260
- // --- Utils ---
405
+ return this.graphqlPaginate(Q_ITERATIONS, {
406
+ fullPath: groupId,
407
+ state: params?.state ?? null,
408
+ search: params?.search ?? null,
409
+ },
410
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
411
+ (d) => d.group?.iterations, mapIteration);
412
+ }
413
+ // ---------------------------------------------------------------------------
414
+ // Utils
415
+ // ---------------------------------------------------------------------------
261
416
  async listProjects(groupId, params) {
262
- const queryParams = {};
263
- if (params?.search)
264
- queryParams["search"] = params.search;
265
- if (params?.archived)
266
- queryParams["archived"] = params.archived;
267
- return this.paginate(`/groups/${encodeURIComponent(groupId)}/projects`, queryParams);
417
+ return this.graphqlPaginate(Q_PROJECTS, {
418
+ fullPath: groupId,
419
+ search: params?.search ?? null,
420
+ includeSubgroups: true,
421
+ },
422
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
423
+ (d) => d.group?.projects, mapProject);
268
424
  }
269
425
  async listGroupMembers(groupId, params) {
270
- const queryParams = {};
271
- if (params?.search)
272
- queryParams["search"] = params.search;
273
- return this.paginate(`/groups/${encodeURIComponent(groupId)}/members`, queryParams);
426
+ return this.graphqlPaginate(Q_MEMBERS, { fullPath: groupId, search: params?.search ?? null },
427
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
428
+ (d) => d.group?.groupMembers, mapMember);
274
429
  }
275
430
  async listGroupLabels(groupId, params) {
276
- const queryParams = {};
277
- if (params?.search)
278
- queryParams["search"] = params.search;
279
- return this.paginate(`/groups/${encodeURIComponent(groupId)}/labels`, queryParams);
431
+ return this.graphqlPaginate(Q_LABELS, { fullPath: groupId, searchTerm: params?.search ?? null },
432
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
433
+ (d) => d.group?.labels, mapLabel);
280
434
  }
281
435
  async listGroupBoards(groupId) {
282
- return this.paginate(`/groups/${encodeURIComponent(groupId)}/boards`);
436
+ return this.graphqlPaginate(Q_BOARDS, { fullPath: groupId },
437
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
438
+ (d) => d.group?.boards, mapBoard);
283
439
  }
284
440
  async getCurrentUser() {
285
- return this.request("GET", "/user");
441
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
442
+ const data = await this.graphql(Q_CURRENT_USER);
443
+ if (!data.currentUser)
444
+ throw new Error("Authentification echouee. Verifiez votre GITLAB_TOKEN.");
445
+ return mapUser(data.currentUser);
286
446
  }
287
447
  }
288
448
  //# sourceMappingURL=client.js.map