@zereight/mcp-gitlab 2.1.64 → 2.1.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -89,7 +89,7 @@ import { formatPrometheusMetrics } from "./server/metrics.js";
89
89
  import { SERVER_VERSION } from "./server/version.js";
90
90
  import { isInitializationRequestBody, isUnauthenticatedDiscoveryRequestBody, readAcceptHeader, readMcpSessionIdHeader, redactSessionIdForLog, } from "./server/request-helpers.js";
91
91
  export { readMcpSessionIdHeader } from "./server/request-helpers.js";
92
- import { normalizeGitLabApiUrl } from "./utils/url.js";
92
+ import { encodeGitLabPath, encodeGitLabPathSegment, normalizeGitLabApiUrl, } from "./utils/url.js";
93
93
  import { estimateMergeCommitCount, filterDiffsByPatterns, openSafeOutputWriteStream, readSafeExistingFile, summarizeWebhookEvents, } from "./utils/helpers.js";
94
94
  import { graphqlQueryContainsWriteOperation, graphqlQueryContainsDeleteOperation, } from "./utils/graphql-query.js";
95
95
  import { resolveNestedWikiUpdateTitle } from "./utils/wiki-title.js";
@@ -98,6 +98,7 @@ import { redactSensitiveGitLabFields } from "./utils/redact-sensitive.js";
98
98
  import { createMaskingPolicyResolver, getManagedMaskingProjectIds, } from "./masking/index.js";
99
99
  import { checkForNewVersion } from "./utils/version-check.js";
100
100
  import { assertGitLabVersionAtLeast } from "./utils/gitlab-version-gate.js";
101
+ import { fetchWithValidatedRedirects } from "./utils/safe-redirect-fetch.js";
101
102
  import { parseGitLabVersionApiResponse, } from "./utils/gitlab-instance-version.js";
102
103
  import { buildBulkPublishDraftNotesBody, needsGitLab19_2BulkPublish, } from "./utils/bulk-publish-options.js";
103
104
  import { appendFilterParam, appendFilterParams, cleanMutuallyExclusiveIdUsernameOptions, dropBlankArrayEntries, isBlankFilterValue, LIST_MERGE_REQUESTS_ID_USERNAME_PAIRS, sanitizeToolArguments, } from "./utils/tool-args.js";
@@ -829,7 +830,7 @@ async function ensureValidOAuthToken() {
829
830
  throw error;
830
831
  }
831
832
  }
832
- // Permission mode gate: readonly exposes only read tools; modify blocks delete tools; full allows all
833
+ // Permission mode gate: readonly exposes only read tools; modify blocks delete/teardown tools; full allows all
833
834
  function isToolAllowedByPermissionMode(toolName) {
834
835
  switch (GITLAB_PERMISSION_MODE) {
835
836
  case "readonly":
@@ -1165,6 +1166,21 @@ const getFetchConfig = () => {
1165
1166
  dispatcher,
1166
1167
  };
1167
1168
  };
1169
+ /**
1170
+ * Options for the download sinks that stream a file through `fetchWithValidatedRedirects`.
1171
+ *
1172
+ * GitLab answers a release asset, a job artifact or an uploaded file with a redirect to
1173
+ * its object storage, so those hops leave the origin and must be validated. The client
1174
+ * split matters as much as the headers: the wrapped client adds the cookie jar and a
1175
+ * fresh `Authorization` on a `401`, so a hop whose credentials were withheld has to use
1176
+ * plain undici instead.
1177
+ */
1178
+ const downloadRedirectOptions = () => ({
1179
+ ...getFetchConfig(),
1180
+ fetchImpl: fetch,
1181
+ unauthenticatedFetchImpl: undiciFetch,
1182
+ isTrustedRedirectHost: isTrustedGitLabRedirectHost,
1183
+ });
1168
1184
  // Compute at startup
1169
1185
  const enabledToolsets = parseEnabledToolsets(GITLAB_TOOLSETS_RAW);
1170
1186
  const individuallyEnabledTools = parseIndividualTools(GITLAB_TOOLS_RAW);
@@ -1195,23 +1211,6 @@ function parseAllowedGitLabApiUrls(value) {
1195
1211
  .map(toAllowedGitLabApiUrl)
1196
1212
  .filter((entry) => Boolean(entry));
1197
1213
  }
1198
- function encodeGitLabPathSegment(value) {
1199
- const segment = String(value);
1200
- let decodedSegment;
1201
- try {
1202
- decodedSegment = decodeURIComponent(segment);
1203
- }
1204
- catch {
1205
- decodedSegment = segment;
1206
- }
1207
- if (decodedSegment === "." || decodedSegment === "..") {
1208
- throw new Error("GitLab URL path segments cannot be '.' or '..'");
1209
- }
1210
- return encodeURIComponent(decodedSegment);
1211
- }
1212
- function encodeGitLabPath(value) {
1213
- return value.split("/").map(encodeGitLabPathSegment).join("/");
1214
- }
1215
1214
  function resolveTrustedGitLabApiUrl(value) {
1216
1215
  const parsed = new URL(normalizeGitLabApiUrl(value));
1217
1216
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
@@ -1238,6 +1237,17 @@ for (const { host, apiUrl } of [
1238
1237
  GITLAB_ALLOWED_API_URLS_BY_HOST.set(host, apiUrl);
1239
1238
  }
1240
1239
  }
1240
+ /**
1241
+ * Redirects from the GitLab API to operator-declared GitLab hosts are followed even
1242
+ * when the host resolves to a private address (self-hosted instances and their
1243
+ * storage may live on an internal network), and those hosts keep receiving the
1244
+ * request credentials. Every other redirect target must resolve to a public
1245
+ * address and is fetched without credentials.
1246
+ *
1247
+ * The lookup key is `URL.host`, which keeps a non-default port, matching how this
1248
+ * map is built from GITLAB_API_URL / GITLAB_ALLOWED_HOSTS.
1249
+ */
1250
+ const isTrustedGitLabRedirectHost = (host) => GITLAB_ALLOWED_API_URLS_BY_HOST.has(host);
1241
1251
  const GITLAB_PROJECT_ID = process.env.GITLAB_PROJECT_ID;
1242
1252
  const GITLAB_ALLOWED_PROJECT_IDS = process.env.GITLAB_ALLOWED_PROJECT_IDS?.split(",")
1243
1253
  .map(id => id.trim())
@@ -1364,7 +1374,7 @@ function rejectIfProjectScopedDeployment(toolName) {
1364
1374
  async function forkProject(projectId, namespace) {
1365
1375
  projectId = decodeURIComponent(projectId); // Decode project ID
1366
1376
  const effectiveProjectId = getEffectiveProjectId(projectId);
1367
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/fork`);
1377
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/fork`);
1368
1378
  if (namespace) {
1369
1379
  url.searchParams.append("namespace", namespace);
1370
1380
  }
@@ -1391,7 +1401,7 @@ async function forkProject(projectId, namespace) {
1391
1401
  async function createBranch(projectId, options) {
1392
1402
  projectId = decodeURIComponent(projectId); // Decode project ID
1393
1403
  const effectiveProjectId = getEffectiveProjectId(projectId);
1394
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/repository/branches`);
1404
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/repository/branches`);
1395
1405
  const response = await fetch(url.toString(), {
1396
1406
  ...getFetchConfig(),
1397
1407
  method: "POST",
@@ -1413,7 +1423,7 @@ async function createBranch(projectId, options) {
1413
1423
  async function getDefaultBranchRef(projectId) {
1414
1424
  projectId = decodeURIComponent(projectId); // Decode project ID
1415
1425
  const effectiveProjectId = getEffectiveProjectId(projectId);
1416
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}`);
1426
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}`);
1417
1427
  const response = await fetch(url.toString(), {
1418
1428
  ...getFetchConfig(),
1419
1429
  });
@@ -1433,12 +1443,12 @@ async function getDefaultBranchRef(projectId) {
1433
1443
  async function getFileContents(projectId, filePath, ref) {
1434
1444
  const decodedProjectId = projectId ? decodeURIComponent(projectId) : "";
1435
1445
  const effectiveProjectId = getEffectiveProjectId(decodedProjectId);
1436
- const encodedPath = encodeURIComponent(filePath);
1446
+ const encodedPath = encodeGitLabPathSegment(filePath);
1437
1447
  // Fall back to default branch if ref is not provided
1438
1448
  if (!ref) {
1439
1449
  ref = await getDefaultBranchRef(decodedProjectId);
1440
1450
  }
1441
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/repository/files/${encodedPath}`);
1451
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/repository/files/${encodedPath}`);
1442
1452
  url.searchParams.append("ref", ref);
1443
1453
  const response = await fetch(url.toString(), {
1444
1454
  ...getFetchConfig(),
@@ -1464,7 +1474,7 @@ async function getFileContents(projectId, filePath, ref) {
1464
1474
  async function createIssue(projectId, options) {
1465
1475
  projectId = decodeURIComponent(projectId); // Decode project ID
1466
1476
  const effectiveProjectId = getEffectiveProjectId(projectId);
1467
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/issues`);
1477
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/issues`);
1468
1478
  // Build request body, converting labels array to comma-separated string
1469
1479
  const body = { ...options };
1470
1480
  if (body.labels && Array.isArray(body.labels)) {
@@ -1499,7 +1509,7 @@ async function listIssues(projectId, options = {}) {
1499
1509
  // resolve the default project (or fail for a multi-project allowlist)
1500
1510
  const decodedProjectId = projectId ? decodeURIComponent(projectId) : "";
1501
1511
  const effectiveProjectId = getEffectiveProjectId(decodedProjectId);
1502
- url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/issues`);
1512
+ url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/issues`);
1503
1513
  }
1504
1514
  else {
1505
1515
  url = new URL(`${getEffectiveApiUrl()}/issues`);
@@ -1600,7 +1610,7 @@ async function listMergeRequests(projectId, options = {}) {
1600
1610
  // In strict scope, never fall back to the instance-wide endpoint;
1601
1611
  // resolve the default project (or fail for a multi-project allowlist)
1602
1612
  const effectiveProjectId = getEffectiveProjectId(decodedProjectId);
1603
- url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/merge_requests`);
1613
+ url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/merge_requests`);
1604
1614
  }
1605
1615
  else {
1606
1616
  url = new URL(`${getEffectiveApiUrl()}/merge_requests`);
@@ -1622,7 +1632,7 @@ async function listMergeRequests(projectId, options = {}) {
1622
1632
  */
1623
1633
  async function listGroupMergeRequests(groupId, options = {}) {
1624
1634
  const decodedGroupId = decodeURIComponent(groupId);
1625
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(decodedGroupId)}/merge_requests`);
1635
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(decodedGroupId)}/merge_requests`);
1626
1636
  appendMergeRequestFilters(url, options);
1627
1637
  const response = await fetch(url.toString(), {
1628
1638
  ...getFetchConfig(),
@@ -1641,7 +1651,7 @@ async function listGroupMergeRequests(groupId, options = {}) {
1641
1651
  */
1642
1652
  async function getIssue(projectId, issueIid) {
1643
1653
  projectId = decodeURIComponent(projectId); // Decode project ID
1644
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}`);
1654
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}`);
1645
1655
  const response = await fetch(url.toString(), {
1646
1656
  ...getFetchConfig(),
1647
1657
  });
@@ -1660,7 +1670,7 @@ async function getIssue(projectId, issueIid) {
1660
1670
  */
1661
1671
  async function updateIssue(projectId, issueIid, options) {
1662
1672
  projectId = decodeURIComponent(projectId); // Decode project ID
1663
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}`);
1673
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}`);
1664
1674
  // Convert labels array to comma-separated string if present
1665
1675
  const body = { ...options };
1666
1676
  if (body.labels && Array.isArray(body.labels)) {
@@ -1685,7 +1695,7 @@ async function updateIssue(projectId, issueIid, options) {
1685
1695
  */
1686
1696
  async function deleteIssue(projectId, issueIid) {
1687
1697
  projectId = decodeURIComponent(projectId); // Decode project ID
1688
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}`);
1698
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}`);
1689
1699
  const response = await fetch(url.toString(), {
1690
1700
  ...getFetchConfig(),
1691
1701
  method: "DELETE",
@@ -2298,7 +2308,7 @@ async function resolveProjectOrGroupPath(projectId) {
2298
2308
  }
2299
2309
  const effectiveProjectId = getEffectiveProjectId(requestedProjectId);
2300
2310
  if (explicitKind === "group") {
2301
- const groupUrl = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(effectiveProjectId)}`);
2311
+ const groupUrl = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(effectiveProjectId)}`);
2302
2312
  const groupResponse = await fetch(groupUrl.toString(), {
2303
2313
  ...getFetchConfig(),
2304
2314
  });
@@ -2306,7 +2316,7 @@ async function resolveProjectOrGroupPath(projectId) {
2306
2316
  const group = await groupResponse.json();
2307
2317
  return { path: group.full_path, kind: "group" };
2308
2318
  }
2309
- const projectUrl = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}`);
2319
+ const projectUrl = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}`);
2310
2320
  const projectResponse = await fetch(projectUrl.toString(), {
2311
2321
  ...getFetchConfig(),
2312
2322
  });
@@ -2318,7 +2328,7 @@ async function resolveProjectOrGroupPath(projectId) {
2318
2328
  !explicitKind &&
2319
2329
  getEffectiveAllowedProjectIds().length === 0 &&
2320
2330
  !/^\d+$/.test(effectiveProjectId)) {
2321
- const groupUrl = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(effectiveProjectId)}`);
2331
+ const groupUrl = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(effectiveProjectId)}`);
2322
2332
  const groupResponse = await fetch(groupUrl.toString(), {
2323
2333
  ...getFetchConfig(),
2324
2334
  });
@@ -3059,7 +3069,7 @@ async function updateWorkItem(projectId, iid, options) {
3059
3069
  */
3060
3070
  async function listIssueLinks(projectId, issueIid) {
3061
3071
  projectId = decodeURIComponent(projectId); // Decode project ID
3062
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}/links`);
3072
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}/links`);
3063
3073
  const response = await fetch(url.toString(), {
3064
3074
  ...getFetchConfig(),
3065
3075
  });
@@ -3078,7 +3088,7 @@ async function listIssueLinks(projectId, issueIid) {
3078
3088
  */
3079
3089
  async function getIssueLink(projectId, issueIid, issueLinkId) {
3080
3090
  projectId = decodeURIComponent(projectId); // Decode project ID
3081
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}/links/${encodeGitLabPathSegment(issueLinkId)}`);
3091
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}/links/${encodeGitLabPathSegment(issueLinkId)}`);
3082
3092
  const response = await fetch(url.toString(), {
3083
3093
  ...getFetchConfig(),
3084
3094
  });
@@ -3100,7 +3110,7 @@ async function getIssueLink(projectId, issueIid, issueLinkId) {
3100
3110
  async function createIssueLink(projectId, issueIid, targetProjectId, targetIssueIid, linkType = "relates_to") {
3101
3111
  projectId = decodeURIComponent(projectId); // Decode project ID
3102
3112
  targetProjectId = decodeURIComponent(targetProjectId); // Decode target project ID as well
3103
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}/links`);
3113
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}/links`);
3104
3114
  const response = await fetch(url.toString(), {
3105
3115
  ...getFetchConfig(),
3106
3116
  method: "POST",
@@ -3125,7 +3135,7 @@ async function createIssueLink(projectId, issueIid, targetProjectId, targetIssue
3125
3135
  */
3126
3136
  async function deleteIssueLink(projectId, issueIid, issueLinkId) {
3127
3137
  projectId = decodeURIComponent(projectId); // Decode project ID
3128
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}/links/${encodeGitLabPathSegment(issueLinkId)}`);
3138
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}/links/${encodeGitLabPathSegment(issueLinkId)}`);
3129
3139
  const response = await fetch(url.toString(), {
3130
3140
  ...getFetchConfig(),
3131
3141
  method: "DELETE",
@@ -3142,7 +3152,7 @@ async function deleteIssueLink(projectId, issueIid, issueLinkId) {
3142
3152
  */
3143
3153
  async function createMergeRequest(projectId, options) {
3144
3154
  projectId = decodeURIComponent(projectId); // Decode project ID
3145
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests`);
3155
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests`);
3146
3156
  const response = await fetch(url.toString(), {
3147
3157
  ...getFetchConfig(),
3148
3158
  method: "POST",
@@ -3184,7 +3194,7 @@ async function createMergeRequest(projectId, options) {
3184
3194
  */
3185
3195
  async function listDiscussions(projectId, resourceType, resourceIid, options = {}) {
3186
3196
  projectId = decodeURIComponent(projectId); // Decode project ID
3187
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/${resourceType}/${encodeGitLabPathSegment(resourceIid)}/discussions`);
3197
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/${resourceType}/${encodeGitLabPathSegment(resourceIid)}/discussions`);
3188
3198
  // Add query parameters for pagination and sorting
3189
3199
  if (options.page) {
3190
3200
  url.searchParams.append("page", options.page.toString());
@@ -3248,7 +3258,7 @@ async function listIssueDiscussions(projectId, issueIid, options = {}) {
3248
3258
  }
3249
3259
  async function deleteMergeRequestDiscussionNote(projectId, mergeRequestIid, discussionId, noteId) {
3250
3260
  projectId = decodeURIComponent(projectId); // Decode project ID
3251
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}/notes/${encodeGitLabPathSegment(noteId)}`);
3261
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}/notes/${encodeGitLabPathSegment(noteId)}`);
3252
3262
  const response = await fetch(url.toString(), {
3253
3263
  ...getFetchConfig(),
3254
3264
  method: "DELETE",
@@ -3272,7 +3282,7 @@ async function deleteMergeRequestDiscussionNote(projectId, mergeRequestIid, disc
3272
3282
  */
3273
3283
  async function updateMergeRequestDiscussionNote(projectId, mergeRequestIid, discussionId, noteId, body, resolved) {
3274
3284
  projectId = decodeURIComponent(projectId); // Decode project ID
3275
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}/notes/${encodeGitLabPathSegment(noteId)}`);
3285
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}/notes/${encodeGitLabPathSegment(noteId)}`);
3276
3286
  // Only one of body or resolved can be sent according to GitLab API
3277
3287
  const payload = {};
3278
3288
  if (body !== undefined) {
@@ -3318,7 +3328,7 @@ async function updateMergeRequestDiscussionNote(projectId, mergeRequestIid, disc
3318
3328
  */
3319
3329
  async function updateIssueNote(projectId, issueIid, discussionId, noteId, body, resolved) {
3320
3330
  projectId = decodeURIComponent(projectId); // Decode project ID
3321
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}/discussions/${encodeGitLabPathSegment(discussionId)}/notes/${encodeGitLabPathSegment(noteId)}`);
3331
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}/discussions/${encodeGitLabPathSegment(discussionId)}/notes/${encodeGitLabPathSegment(noteId)}`);
3322
3332
  // Only one of body or resolved can be sent according to GitLab API
3323
3333
  const payload = {};
3324
3334
  if (body !== undefined) {
@@ -3347,7 +3357,7 @@ async function updateIssueNote(projectId, issueIid, discussionId, noteId, body,
3347
3357
  */
3348
3358
  async function createIssueNote(projectId, issueIid, discussionId, body, createdAt) {
3349
3359
  projectId = decodeURIComponent(projectId); // Decode project ID
3350
- const basePath = `${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}`;
3360
+ const basePath = `${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}`;
3351
3361
  const url = new URL(discussionId ? `${basePath}/discussions/${encodeGitLabPathSegment(discussionId)}/notes` : `${basePath}/notes`);
3352
3362
  const payload = { body };
3353
3363
  if (createdAt) {
@@ -3375,7 +3385,7 @@ async function createIssueNote(projectId, issueIid, discussionId, body, createdA
3375
3385
  */
3376
3386
  async function createMergeRequestDiscussionNote(projectId, mergeRequestIid, discussionId, body, createdAt) {
3377
3387
  projectId = decodeURIComponent(projectId); // Decode project ID
3378
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}/notes`);
3388
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}/notes`);
3379
3389
  const payload = { body };
3380
3390
  if (createdAt) {
3381
3391
  payload.created_at = createdAt;
@@ -3391,7 +3401,7 @@ async function createMergeRequestDiscussionNote(projectId, mergeRequestIid, disc
3391
3401
  }
3392
3402
  async function createMergeRequestNote(projectId, mergeRequestIid, body) {
3393
3403
  projectId = decodeURIComponent(projectId); // Decode project ID
3394
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes`);
3404
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes`);
3395
3405
  const payload = {
3396
3406
  id: projectId,
3397
3407
  merge_request_iid: mergeRequestIid,
@@ -3408,7 +3418,7 @@ async function createMergeRequestNote(projectId, mergeRequestIid, body) {
3408
3418
  }
3409
3419
  async function deleteMergeRequestNote(projectId, mergeRequestIid, noteId) {
3410
3420
  projectId = decodeURIComponent(projectId); // Decode project ID
3411
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes/${encodeGitLabPathSegment(noteId)}`);
3421
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes/${encodeGitLabPathSegment(noteId)}`);
3412
3422
  const response = await fetch(url.toString(), {
3413
3423
  ...getFetchConfig(),
3414
3424
  method: "DELETE",
@@ -3421,7 +3431,7 @@ async function deleteMergeRequestNote(projectId, mergeRequestIid, noteId) {
3421
3431
  // --- Emoji Reactions (REST) ---
3422
3432
  function buildAwardEmojiPath(entity, projectId, entityIid, opts) {
3423
3433
  projectId = decodeURIComponent(projectId);
3424
- const pp = encodeURIComponent(getEffectiveProjectId(projectId));
3434
+ const pp = encodeGitLabPathSegment(getEffectiveProjectId(projectId));
3425
3435
  let path = `${getEffectiveApiUrl()}/projects/${pp}/${entity}/${encodeGitLabPathSegment(entityIid)}`;
3426
3436
  if (opts?.noteId) {
3427
3437
  path = opts.discussionId
@@ -3456,7 +3466,7 @@ async function deleteRestAwardEmoji(path) {
3456
3466
  }
3457
3467
  async function getMergeRequestNote(projectId, mergeRequestIid, noteId) {
3458
3468
  projectId = decodeURIComponent(projectId); // Decode project ID
3459
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes/${encodeGitLabPathSegment(noteId)}`);
3469
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes/${encodeGitLabPathSegment(noteId)}`);
3460
3470
  const response = await fetch(url.toString(), {
3461
3471
  ...getFetchConfig(),
3462
3472
  method: "GET",
@@ -3467,7 +3477,7 @@ async function getMergeRequestNote(projectId, mergeRequestIid, noteId) {
3467
3477
  }
3468
3478
  async function getMergeRequestDiscussion(projectId, mergeRequestIid, discussionId) {
3469
3479
  projectId = decodeURIComponent(projectId); // Decode project ID
3470
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}`);
3480
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}`);
3471
3481
  const response = await fetch(url.toString(), {
3472
3482
  ...getFetchConfig(),
3473
3483
  method: "GET",
@@ -3478,7 +3488,7 @@ async function getMergeRequestDiscussion(projectId, mergeRequestIid, discussionI
3478
3488
  }
3479
3489
  async function getMergeRequestNotes(projectId, mergeRequestIid, sort, order_by, per_page, page) {
3480
3490
  projectId = decodeURIComponent(projectId); // Decode project ID
3481
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes`);
3491
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes`);
3482
3492
  if (sort) {
3483
3493
  url.searchParams.append("sort", sort);
3484
3494
  }
@@ -3501,7 +3511,7 @@ async function getMergeRequestNotes(projectId, mergeRequestIid, sort, order_by,
3501
3511
  }
3502
3512
  async function updateMergeRequestNote(projectId, mergeRequestIid, noteId, body) {
3503
3513
  projectId = decodeURIComponent(projectId); // Decode project ID
3504
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes/${encodeGitLabPathSegment(noteId)}`);
3514
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes/${encodeGitLabPathSegment(noteId)}`);
3505
3515
  const payload = {
3506
3516
  id: projectId,
3507
3517
  merge_request_iid: mergeRequestIid,
@@ -3531,8 +3541,8 @@ async function updateMergeRequestNote(projectId, mergeRequestIid, noteId, body)
3531
3541
  */
3532
3542
  async function createOrUpdateFile(projectId, filePath, content, commitMessage, branch, previousPath, last_commit_id, commit_id, encoding) {
3533
3543
  projectId = decodeURIComponent(projectId); // Decode project ID
3534
- const encodedPath = encodeURIComponent(filePath);
3535
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/files/${encodedPath}`);
3544
+ const encodedPath = encodeGitLabPathSegment(filePath);
3545
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/files/${encodedPath}`);
3536
3546
  const resolvedEncoding = encoding ?? GITLAB_REPO_FILE_ENCODING;
3537
3547
  const body = {
3538
3548
  branch,
@@ -3603,7 +3613,7 @@ async function createOrUpdateFile(projectId, filePath, content, commitMessage, b
3603
3613
  */
3604
3614
  async function createCommit(projectId, message, branch, actions) {
3605
3615
  projectId = decodeURIComponent(projectId); // Decode project ID
3606
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits`);
3616
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/commits`);
3607
3617
  const response = await fetch(url.toString(), {
3608
3618
  ...getFetchConfig(),
3609
3619
  method: "POST",
@@ -3672,7 +3682,7 @@ async function searchBlobs(params) {
3672
3682
  let basePath;
3673
3683
  if (params.project_id) {
3674
3684
  const decodedProjectId = decodeURIComponent(params.project_id);
3675
- const projectId = encodeURIComponent(getEffectiveProjectId(decodedProjectId));
3685
+ const projectId = encodeGitLabPathSegment(getEffectiveProjectId(decodedProjectId));
3676
3686
  basePath = `${getEffectiveApiUrl()}/projects/${projectId}/search`;
3677
3687
  }
3678
3688
  else if (params.group_id) {
@@ -3750,11 +3760,11 @@ async function getMergeRequest(projectId, mergeRequestIid, branchName) {
3750
3760
  projectId = decodeURIComponent(projectId); // Decode project ID
3751
3761
  let url;
3752
3762
  if (mergeRequestIid) {
3753
- url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}`);
3763
+ url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}`);
3754
3764
  url.searchParams.append("include_diverged_commits_count", "true");
3755
3765
  }
3756
3766
  else if (branchName) {
3757
- url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests?source_branch=${encodeURIComponent(branchName)}`);
3767
+ url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests?source_branch=${encodeURIComponent(branchName)}`);
3758
3768
  }
3759
3769
  else {
3760
3770
  throw new Error("Either mergeRequestIid or branchName must be provided");
@@ -3772,7 +3782,7 @@ async function getMergeRequest(projectId, mergeRequestIid, branchName) {
3772
3782
  return GitLabMergeRequestSchema.parse(data);
3773
3783
  }
3774
3784
  async function getMergeRequestSourceCommitCount(projectId, mergeRequestIid) {
3775
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/commits`);
3785
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/commits`);
3776
3786
  url.searchParams.append("per_page", "100");
3777
3787
  let totalCount = 0;
3778
3788
  let page = 1;
@@ -3800,7 +3810,7 @@ async function getMergeRequestSourceCommitCount(projectId, mergeRequestIid) {
3800
3810
  }
3801
3811
  async function listMergeRequestPipelines(projectId, mergeRequestIid, options = {}) {
3802
3812
  projectId = decodeURIComponent(projectId);
3803
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/pipelines`);
3813
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/pipelines`);
3804
3814
  appendFilterParams(url.searchParams, options);
3805
3815
  const response = await fetch(url.toString(), {
3806
3816
  ...getFetchConfig(),
@@ -3810,7 +3820,7 @@ async function listMergeRequestPipelines(projectId, mergeRequestIid, options = {
3810
3820
  return z.array(GitLabMergeRequestPipelineSchema).parse(data);
3811
3821
  }
3812
3822
  async function getProjectMergeMethod(projectId) {
3813
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}`);
3823
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}`);
3814
3824
  const response = await fetch(url.toString(), {
3815
3825
  ...getFetchConfig(),
3816
3826
  });
@@ -3997,7 +4007,7 @@ async function getMergeRequestDiffs(projectId, mergeRequestIid, branchName, view
3997
4007
  const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
3998
4008
  mergeRequestIid = mergeRequest.iid;
3999
4009
  }
4000
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/changes`);
4010
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/changes`);
4001
4011
  if (view) {
4002
4012
  url.searchParams.append("view", view);
4003
4013
  }
@@ -4027,7 +4037,7 @@ async function listMergeRequestDiffs(projectId, mergeRequestIid, branchName, pag
4027
4037
  const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
4028
4038
  mergeRequestIid = mergeRequest.iid;
4029
4039
  }
4030
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/diffs`);
4040
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/diffs`);
4031
4041
  if (page) {
4032
4042
  url.searchParams.append("page", page.toString());
4033
4043
  }
@@ -4063,7 +4073,7 @@ async function listMergeRequestChangedFiles(projectId, mergeRequestIid, branchNa
4063
4073
  const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
4064
4074
  mergeRequestIid = mergeRequest.iid;
4065
4075
  }
4066
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/changes`);
4076
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/changes`);
4067
4077
  const response = await fetch(url.toString(), { ...getFetchConfig() });
4068
4078
  await handleGitLabError(response);
4069
4079
  const data = (await response.json());
@@ -4104,7 +4114,7 @@ async function getMergeRequestFileDiff(projectId, filePaths, mergeRequestIid, br
4104
4114
  let page = 1;
4105
4115
  const perPage = 20;
4106
4116
  while (remaining.size > 0) {
4107
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/diffs`);
4117
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/diffs`);
4108
4118
  url.searchParams.append("page", page.toString());
4109
4119
  url.searchParams.append("per_page", perPage.toString());
4110
4120
  if (unidiff) {
@@ -4147,7 +4157,7 @@ async function getMergeRequestFileDiff(projectId, filePaths, mergeRequestIid, br
4147
4157
  */
4148
4158
  async function getBranchDiffs(projectId, from, to, straight) {
4149
4159
  projectId = decodeURIComponent(projectId); // Decode project ID
4150
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/compare`);
4160
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/compare`);
4151
4161
  url.searchParams.append("from", from);
4152
4162
  url.searchParams.append("to", to);
4153
4163
  if (straight !== undefined) {
@@ -4182,7 +4192,7 @@ async function updateMergeRequest(projectId, options, mergeRequestIid, branchNam
4182
4192
  const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
4183
4193
  mergeRequestIid = mergeRequest.iid;
4184
4194
  }
4185
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}`);
4195
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}`);
4186
4196
  const response = await fetch(url.toString(), {
4187
4197
  ...getFetchConfig(),
4188
4198
  method: "PUT",
@@ -4202,7 +4212,7 @@ async function updateMergeRequest(projectId, options, mergeRequestIid, branchNam
4202
4212
  */
4203
4213
  async function mergeMergeRequest(projectId, options, mergeRequestIid) {
4204
4214
  projectId = decodeURIComponent(projectId); // Decode project ID
4205
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/merge`);
4215
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/merge`);
4206
4216
  const response = await fetch(url.toString(), {
4207
4217
  ...getFetchConfig(),
4208
4218
  method: "PUT",
@@ -4222,7 +4232,7 @@ async function mergeMergeRequest(projectId, options, mergeRequestIid) {
4222
4232
  */
4223
4233
  async function approveMergeRequest(projectId, mergeRequestIid, sha, approvalPassword) {
4224
4234
  projectId = decodeURIComponent(projectId);
4225
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/approve`);
4235
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/approve`);
4226
4236
  const body = {};
4227
4237
  if (sha) {
4228
4238
  body.sha = sha;
@@ -4247,7 +4257,7 @@ async function approveMergeRequest(projectId, mergeRequestIid, sha, approvalPass
4247
4257
  */
4248
4258
  async function unapproveMergeRequest(projectId, mergeRequestIid) {
4249
4259
  projectId = decodeURIComponent(projectId);
4250
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/unapprove`);
4260
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/unapprove`);
4251
4261
  const response = await fetch(url.toString(), {
4252
4262
  ...getFetchConfig(),
4253
4263
  method: "POST",
@@ -4265,7 +4275,7 @@ async function unapproveMergeRequest(projectId, mergeRequestIid) {
4265
4275
  */
4266
4276
  async function getMergeRequestApprovalState(projectId, mergeRequestIid) {
4267
4277
  projectId = decodeURIComponent(projectId);
4268
- const approvalStateUrl = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/approval_state`);
4278
+ const approvalStateUrl = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/approval_state`);
4269
4279
  const approvalStateResponse = await fetch(approvalStateUrl.toString(), {
4270
4280
  ...getFetchConfig(),
4271
4281
  method: "GET",
@@ -4313,7 +4323,7 @@ async function getMergeRequestApprovalState(projectId, mergeRequestIid) {
4313
4323
  */
4314
4324
  async function getMergeRequestConflicts(projectId, mergeRequestIid) {
4315
4325
  projectId = decodeURIComponent(projectId);
4316
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/conflicts`);
4326
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/conflicts`);
4317
4327
  const response = await fetch(url.toString(), {
4318
4328
  ...getFetchConfig(),
4319
4329
  method: "GET",
@@ -4322,7 +4332,7 @@ async function getMergeRequestConflicts(projectId, mergeRequestIid) {
4322
4332
  return (await response.json());
4323
4333
  }
4324
4334
  async function getMergeRequestApprovalsFallback(projectId, mergeRequestIid) {
4325
- const approvalsUrl = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/approvals`);
4335
+ const approvalsUrl = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/approvals`);
4326
4336
  const approvalsResponse = await fetch(approvalsUrl.toString(), {
4327
4337
  ...getFetchConfig(),
4328
4338
  method: "GET",
@@ -4382,7 +4392,7 @@ async function createNote(projectId, noteableType, // specifies 'issue' or 'merg
4382
4392
  noteableIid, body) {
4383
4393
  projectId = decodeURIComponent(projectId); // Decode project ID
4384
4394
  // ⚙️ Response type can be adjusted according to the GitLab API documentation
4385
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/${noteableType}s/${encodeGitLabPathSegment(noteableIid)}/notes` // Using plural form (issues/merge_requests) as per GitLab API documentation
4395
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/${noteableType}s/${encodeGitLabPathSegment(noteableIid)}/notes` // Using plural form (issues/merge_requests) as per GitLab API documentation
4386
4396
  );
4387
4397
  const response = await fetch(url.toString(), {
4388
4398
  ...getFetchConfig(),
@@ -4403,7 +4413,7 @@ noteableIid, body) {
4403
4413
  */
4404
4414
  async function getDraftNote(project_id, merge_request_iid, draft_note_id) {
4405
4415
  const effectiveProjectId = getEffectiveProjectId(decodeURIComponent(project_id));
4406
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/merge_requests/${encodeGitLabPathSegment(merge_request_iid)}/draft_notes/${encodeGitLabPathSegment(draft_note_id)}`, { ...getFetchConfig() });
4416
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/merge_requests/${encodeGitLabPathSegment(merge_request_iid)}/draft_notes/${encodeGitLabPathSegment(draft_note_id)}`, { ...getFetchConfig() });
4407
4417
  if (!response.ok) {
4408
4418
  const errorText = await response.text();
4409
4419
  throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorText}`);
@@ -4413,7 +4423,7 @@ async function getDraftNote(project_id, merge_request_iid, draft_note_id) {
4413
4423
  }
4414
4424
  async function listDraftNotes(projectId, mergeRequestIid) {
4415
4425
  projectId = decodeURIComponent(projectId);
4416
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes`);
4426
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes`);
4417
4427
  const response = await fetch(url.toString(), {
4418
4428
  ...getFetchConfig(),
4419
4429
  method: "GET",
@@ -4437,7 +4447,7 @@ async function listDraftNotes(projectId, mergeRequestIid) {
4437
4447
  */
4438
4448
  async function createDraftNote(projectId, mergeRequestIid, body, inReplyToDiscussionId, position, resolveDiscussion) {
4439
4449
  projectId = decodeURIComponent(projectId);
4440
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes`);
4450
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes`);
4441
4451
  const requestBody = { note: body };
4442
4452
  if (inReplyToDiscussionId) {
4443
4453
  requestBody.in_reply_to_discussion_id = inReplyToDiscussionId;
@@ -4472,7 +4482,7 @@ async function createDraftNote(projectId, mergeRequestIid, body, inReplyToDiscus
4472
4482
  */
4473
4483
  async function updateDraftNote(projectId, mergeRequestIid, draftNoteId, body, position, resolveDiscussion) {
4474
4484
  projectId = decodeURIComponent(projectId);
4475
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes/${encodeGitLabPathSegment(draftNoteId)}`);
4485
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes/${encodeGitLabPathSegment(draftNoteId)}`);
4476
4486
  const requestBody = {};
4477
4487
  if (body !== undefined) {
4478
4488
  requestBody.note = body;
@@ -4504,7 +4514,7 @@ async function updateDraftNote(projectId, mergeRequestIid, draftNoteId, body, po
4504
4514
  */
4505
4515
  async function deleteDraftNote(projectId, mergeRequestIid, draftNoteId) {
4506
4516
  projectId = decodeURIComponent(projectId);
4507
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes/${encodeGitLabPathSegment(draftNoteId)}`);
4517
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes/${encodeGitLabPathSegment(draftNoteId)}`);
4508
4518
  const response = await fetch(url.toString(), {
4509
4519
  ...getFetchConfig(),
4510
4520
  method: "DELETE",
@@ -4523,7 +4533,7 @@ async function deleteDraftNote(projectId, mergeRequestIid, draftNoteId) {
4523
4533
  */
4524
4534
  async function publishDraftNote(projectId, mergeRequestIid, draftNoteId) {
4525
4535
  projectId = decodeURIComponent(projectId);
4526
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes/${encodeGitLabPathSegment(draftNoteId)}/publish`);
4536
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes/${encodeGitLabPathSegment(draftNoteId)}/publish`);
4527
4537
  const response = await fetch(url.toString(), {
4528
4538
  ...getFetchConfig(),
4529
4539
  method: "PUT",
@@ -4594,7 +4604,7 @@ async function fetchGitLabInstanceVersion() {
4594
4604
  */
4595
4605
  async function bulkPublishDraftNotes(projectId, mergeRequestIid, options = {}) {
4596
4606
  projectId = decodeURIComponent(projectId);
4597
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes/bulk_publish`);
4607
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes/bulk_publish`);
4598
4608
  const body = buildBulkPublishDraftNotesBody(options);
4599
4609
  if (needsGitLab19_2BulkPublish(body)) {
4600
4610
  await assertGitLabVersionAtLeast({
@@ -4632,7 +4642,7 @@ async function bulkPublishDraftNotes(projectId, mergeRequestIid, options = {}) {
4632
4642
  }
4633
4643
  async function resolveMergeRequestThread(projectId, mergeRequestIid, discussionId, resolved) {
4634
4644
  projectId = decodeURIComponent(projectId);
4635
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}`);
4645
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}`);
4636
4646
  if (resolved !== undefined) {
4637
4647
  url.searchParams.append("resolved", resolved ? "true" : "false");
4638
4648
  }
@@ -4664,7 +4674,7 @@ async function resolveMergeRequestThread(projectId, mergeRequestIid, discussionI
4664
4674
  */
4665
4675
  async function createMergeRequestThread(projectId, mergeRequestIid, body, position, createdAt) {
4666
4676
  projectId = decodeURIComponent(projectId); // Decode project ID
4667
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions`);
4677
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions`);
4668
4678
  const payload = { body };
4669
4679
  if (position) {
4670
4680
  payload.position = position;
@@ -4691,7 +4701,7 @@ async function createMergeRequestThread(projectId, mergeRequestIid, body, positi
4691
4701
  */
4692
4702
  async function listMergeRequestVersions(projectId, mergeRequestIid) {
4693
4703
  projectId = decodeURIComponent(projectId); // Decode project ID
4694
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/versions`);
4704
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/versions`);
4695
4705
  const response = await fetch(url.toString(), {
4696
4706
  ...getFetchConfig(),
4697
4707
  });
@@ -4710,7 +4720,7 @@ async function listMergeRequestVersions(projectId, mergeRequestIid) {
4710
4720
  */
4711
4721
  async function getMergeRequestVersion(projectId, mergeRequestIid, versionId, unidiff) {
4712
4722
  projectId = decodeURIComponent(projectId); // Decode project ID
4713
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/versions/${encodeGitLabPathSegment(versionId)}`);
4723
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/versions/${encodeGitLabPathSegment(versionId)}`);
4714
4724
  if (unidiff !== undefined) {
4715
4725
  url.searchParams.append("unidiff", String(unidiff));
4716
4726
  }
@@ -4752,7 +4762,7 @@ async function listProjects(options = {}) {
4752
4762
  async function listLabels(projectId, options = {}) {
4753
4763
  projectId = decodeURIComponent(projectId); // Decode project ID
4754
4764
  // Construct the URL with project path
4755
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/labels`);
4765
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/labels`);
4756
4766
  // Add query parameters
4757
4767
  appendFilterParams(url.searchParams, options);
4758
4768
  // Make the API request
@@ -4775,7 +4785,7 @@ async function listLabels(projectId, options = {}) {
4775
4785
  */
4776
4786
  async function getLabel(projectId, labelId, includeAncestorGroups) {
4777
4787
  projectId = decodeURIComponent(projectId); // Decode project ID
4778
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/labels/${encodeURIComponent(String(labelId))}`);
4788
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/labels/${encodeGitLabPathSegment(String(labelId))}`);
4779
4789
  // Add query parameters
4780
4790
  if (includeAncestorGroups !== undefined) {
4781
4791
  url.searchParams.append("include_ancestor_groups", includeAncestorGroups ? "true" : "false");
@@ -4800,7 +4810,7 @@ async function getLabel(projectId, labelId, includeAncestorGroups) {
4800
4810
  async function createLabel(projectId, options) {
4801
4811
  projectId = decodeURIComponent(projectId); // Decode project ID
4802
4812
  // Make the API request
4803
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/labels`, {
4813
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/labels`, {
4804
4814
  ...getFetchConfig(),
4805
4815
  method: "POST",
4806
4816
  body: JSON.stringify(options),
@@ -4822,7 +4832,7 @@ async function createLabel(projectId, options) {
4822
4832
  async function updateLabel(projectId, labelId, options) {
4823
4833
  projectId = decodeURIComponent(projectId); // Decode project ID
4824
4834
  // Make the API request
4825
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/labels/${encodeURIComponent(String(labelId))}`, {
4835
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/labels/${encodeGitLabPathSegment(String(labelId))}`, {
4826
4836
  ...getFetchConfig(),
4827
4837
  method: "PUT",
4828
4838
  body: JSON.stringify(options),
@@ -4842,7 +4852,7 @@ async function updateLabel(projectId, labelId, options) {
4842
4852
  async function deleteLabel(projectId, labelId) {
4843
4853
  projectId = decodeURIComponent(projectId); // Decode project ID
4844
4854
  // Make the API request
4845
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/labels/${encodeURIComponent(String(labelId))}`, {
4855
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/labels/${encodeGitLabPathSegment(String(labelId))}`, {
4846
4856
  ...getFetchConfig(),
4847
4857
  method: "DELETE",
4848
4858
  });
@@ -4856,7 +4866,7 @@ async function deleteLabel(projectId, labelId) {
4856
4866
  * @returns {Promise<GitLabProject[]>} Array of projects in the group
4857
4867
  */
4858
4868
  async function listGroupProjects(options) {
4859
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(options.group_id)}/projects`);
4869
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(options.group_id)}/projects`);
4860
4870
  // Add optional parameters to URL
4861
4871
  if (options.include_subgroups)
4862
4872
  url.searchParams.append("include_subgroups", "true");
@@ -4906,11 +4916,11 @@ async function listGroupProjects(options) {
4906
4916
  function buildWebhookBaseUrl(projectId, groupId) {
4907
4917
  if (projectId) {
4908
4918
  projectId = decodeURIComponent(projectId);
4909
- return `${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/hooks`;
4919
+ return `${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/hooks`;
4910
4920
  }
4911
4921
  rejectIfStrictProjectScope("group webhooks");
4912
4922
  const decodedGroupId = decodeURIComponent(groupId);
4913
- return `${getEffectiveApiUrl()}/groups/${encodeURIComponent(decodedGroupId)}/hooks`;
4923
+ return `${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(decodedGroupId)}/hooks`;
4914
4924
  }
4915
4925
  /**
4916
4926
  * List webhooks for a project or group
@@ -5025,7 +5035,7 @@ async function getWebhookEvent(options) {
5025
5035
  */
5026
5036
  async function listWikiPages(projectId, options = {}) {
5027
5037
  projectId = decodeURIComponent(projectId); // Decode project ID
5028
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/wikis`);
5038
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/wikis`);
5029
5039
  if (options.page)
5030
5040
  url.searchParams.append("page", options.page.toString());
5031
5041
  if (options.per_page)
@@ -5046,7 +5056,7 @@ async function listWikiPages(projectId, options = {}) {
5046
5056
  */
5047
5057
  async function getWikiPage(projectId, slug, renderHtml) {
5048
5058
  projectId = decodeURIComponent(projectId); // Decode project ID
5049
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/wikis/${encodeURIComponent(slug)}`);
5059
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/wikis/${encodeGitLabPathSegment(slug)}`);
5050
5060
  if (renderHtml)
5051
5061
  url.searchParams.append("render_html", renderHtml.toString());
5052
5062
  const response = await fetch(url.toString(), { ...getFetchConfig() });
@@ -5062,7 +5072,7 @@ async function createWikiPage(projectId, title, content, format) {
5062
5072
  const body = { title, content };
5063
5073
  if (format)
5064
5074
  body.format = format;
5065
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/wikis`, {
5075
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/wikis`, {
5066
5076
  ...getFetchConfig(),
5067
5077
  method: "POST",
5068
5078
  body: JSON.stringify(body),
@@ -5090,7 +5100,7 @@ async function updateWikiPage(projectId, slug, title, content, format) {
5090
5100
  body.content = content;
5091
5101
  if (format)
5092
5102
  body.format = format;
5093
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/wikis/${encodeURIComponent(slug)}`, {
5103
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/wikis/${encodeGitLabPathSegment(slug)}`, {
5094
5104
  ...getFetchConfig(),
5095
5105
  method: "PUT",
5096
5106
  body: JSON.stringify(body),
@@ -5104,7 +5114,7 @@ async function updateWikiPage(projectId, slug, title, content, format) {
5104
5114
  */
5105
5115
  async function deleteWikiPage(projectId, slug) {
5106
5116
  projectId = decodeURIComponent(projectId); // Decode project ID
5107
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/wikis/${encodeURIComponent(slug)}`, {
5117
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/wikis/${encodeGitLabPathSegment(slug)}`, {
5108
5118
  ...getFetchConfig(),
5109
5119
  method: "DELETE",
5110
5120
  });
@@ -5115,7 +5125,7 @@ async function deleteWikiPage(projectId, slug) {
5115
5125
  */
5116
5126
  async function listGroupWikiPages(groupId, options = {}) {
5117
5127
  groupId = decodeURIComponent(groupId); // Decode group ID
5118
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/wikis`);
5128
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/wikis`);
5119
5129
  if (options.page)
5120
5130
  url.searchParams.append("page", options.page.toString());
5121
5131
  if (options.per_page)
@@ -5136,7 +5146,7 @@ async function listGroupWikiPages(groupId, options = {}) {
5136
5146
  */
5137
5147
  async function getGroupWikiPage(groupId, slug, renderHtml) {
5138
5148
  groupId = decodeURIComponent(groupId); // Decode group ID
5139
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/wikis/${encodeURIComponent(slug)}`);
5149
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/wikis/${encodeGitLabPathSegment(slug)}`);
5140
5150
  if (renderHtml)
5141
5151
  url.searchParams.append("render_html", renderHtml.toString());
5142
5152
  const response = await fetch(url.toString(), { ...getFetchConfig() });
@@ -5152,7 +5162,7 @@ async function createGroupWikiPage(groupId, title, content, format) {
5152
5162
  const body = { title, content };
5153
5163
  if (format)
5154
5164
  body.format = format;
5155
- const response = await fetch(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/wikis`, {
5165
+ const response = await fetch(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/wikis`, {
5156
5166
  ...getFetchConfig(),
5157
5167
  method: "POST",
5158
5168
  body: JSON.stringify(body),
@@ -5180,7 +5190,7 @@ async function updateGroupWikiPage(groupId, slug, title, content, format) {
5180
5190
  body.content = content;
5181
5191
  if (format)
5182
5192
  body.format = format;
5183
- const response = await fetch(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/wikis/${encodeURIComponent(slug)}`, {
5193
+ const response = await fetch(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/wikis/${encodeGitLabPathSegment(slug)}`, {
5184
5194
  ...getFetchConfig(),
5185
5195
  method: "PUT",
5186
5196
  body: JSON.stringify(body),
@@ -5194,7 +5204,7 @@ async function updateGroupWikiPage(groupId, slug, title, content, format) {
5194
5204
  */
5195
5205
  async function deleteGroupWikiPage(groupId, slug) {
5196
5206
  groupId = decodeURIComponent(groupId); // Decode group ID
5197
- const response = await fetch(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/wikis/${encodeURIComponent(slug)}`, {
5207
+ const response = await fetch(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/wikis/${encodeGitLabPathSegment(slug)}`, {
5198
5208
  ...getFetchConfig(),
5199
5209
  method: "DELETE",
5200
5210
  });
@@ -5209,7 +5219,7 @@ async function deleteGroupWikiPage(groupId, slug) {
5209
5219
  */
5210
5220
  async function listPipelines(projectId, options = {}) {
5211
5221
  projectId = decodeURIComponent(projectId); // Decode project ID
5212
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines`);
5222
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines`);
5213
5223
  // Add all query parameters
5214
5224
  appendFilterParams(url.searchParams, options);
5215
5225
  const response = await fetch(url.toString(), {
@@ -5228,7 +5238,7 @@ async function listPipelines(projectId, options = {}) {
5228
5238
  */
5229
5239
  async function getPipeline(projectId, pipelineId, signal) {
5230
5240
  projectId = decodeURIComponent(projectId); // Decode project ID
5231
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}`);
5241
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}`);
5232
5242
  const response = await fetch(url.toString(), {
5233
5243
  ...getFetchConfig(),
5234
5244
  signal,
@@ -5242,7 +5252,7 @@ async function getPipeline(projectId, pipelineId, signal) {
5242
5252
  }
5243
5253
  async function getPipelineVariables(projectId, pipelineId, page, perPage) {
5244
5254
  projectId = decodeURIComponent(projectId);
5245
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/variables`);
5255
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/variables`);
5246
5256
  if (page !== undefined)
5247
5257
  url.searchParams.set("page", String(page));
5248
5258
  if (perPage !== undefined)
@@ -5254,7 +5264,7 @@ async function getPipelineVariables(projectId, pipelineId, page, perPage) {
5254
5264
  async function getPipelineReport(projectId, pipelineId, summary = false, page, perPage) {
5255
5265
  projectId = decodeURIComponent(projectId);
5256
5266
  const endpoint = summary ? "test_report_summary" : "test_report";
5257
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/${endpoint}`);
5267
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/${endpoint}`);
5258
5268
  if (page !== undefined)
5259
5269
  url.searchParams.set("page", String(page));
5260
5270
  if (perPage !== undefined)
@@ -5265,13 +5275,13 @@ async function getPipelineReport(projectId, pipelineId, summary = false, page, p
5265
5275
  }
5266
5276
  async function deletePipeline(projectId, pipelineId) {
5267
5277
  projectId = decodeURIComponent(projectId);
5268
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}`, { ...getFetchConfig(), method: "DELETE" });
5278
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}`, { ...getFetchConfig(), method: "DELETE" });
5269
5279
  await handleGitLabError(response);
5270
5280
  return response.status === 204 ? { deleted: true } : response.json();
5271
5281
  }
5272
5282
  async function updatePipelineMetadata(projectId, pipelineId, name) {
5273
5283
  projectId = decodeURIComponent(projectId);
5274
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/metadata`, { ...getFetchConfig(), method: "PUT", body: JSON.stringify({ name }) });
5284
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/metadata`, { ...getFetchConfig(), method: "PUT", body: JSON.stringify({ name }) });
5275
5285
  await handleGitLabError(response);
5276
5286
  return response.json();
5277
5287
  }
@@ -5284,7 +5294,7 @@ async function updatePipelineMetadata(projectId, pipelineId, name) {
5284
5294
  */
5285
5295
  async function listDeployments(projectId, options = {}) {
5286
5296
  projectId = decodeURIComponent(projectId);
5287
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/deployments`);
5297
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/deployments`);
5288
5298
  appendFilterParams(url.searchParams, options);
5289
5299
  const response = await fetch(url.toString(), {
5290
5300
  ...getFetchConfig(),
@@ -5302,7 +5312,7 @@ async function listDeployments(projectId, options = {}) {
5302
5312
  */
5303
5313
  async function getDeployment(projectId, deploymentId) {
5304
5314
  projectId = decodeURIComponent(projectId);
5305
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/deployments/${encodeGitLabPathSegment(deploymentId)}`);
5315
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/deployments/${encodeGitLabPathSegment(deploymentId)}`);
5306
5316
  const response = await fetch(url.toString(), {
5307
5317
  ...getFetchConfig(),
5308
5318
  });
@@ -5315,7 +5325,7 @@ async function getDeployment(projectId, deploymentId) {
5315
5325
  }
5316
5326
  async function deploymentRequest(projectId, path = "", method = "GET", body, query) {
5317
5327
  projectId = decodeURIComponent(projectId);
5318
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/deployments${path}`);
5328
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/deployments${path}`);
5319
5329
  for (const [key, value] of Object.entries(query ?? {})) {
5320
5330
  if (value !== undefined)
5321
5331
  url.searchParams.set(key, String(value));
@@ -5336,7 +5346,7 @@ async function deploymentRequest(projectId, path = "", method = "GET", body, que
5336
5346
  */
5337
5347
  async function listEnvironments(projectId, options = {}) {
5338
5348
  projectId = decodeURIComponent(projectId);
5339
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/environments`);
5349
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/environments`);
5340
5350
  appendFilterParams(url.searchParams, options);
5341
5351
  const response = await fetch(url.toString(), {
5342
5352
  ...getFetchConfig(),
@@ -5354,7 +5364,7 @@ async function listEnvironments(projectId, options = {}) {
5354
5364
  */
5355
5365
  async function getEnvironment(projectId, environmentId) {
5356
5366
  projectId = decodeURIComponent(projectId);
5357
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/environments/${encodeGitLabPathSegment(environmentId)}`);
5367
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/environments/${encodeGitLabPathSegment(environmentId)}`);
5358
5368
  const response = await fetch(url.toString(), {
5359
5369
  ...getFetchConfig(),
5360
5370
  });
@@ -5367,7 +5377,7 @@ async function getEnvironment(projectId, environmentId) {
5367
5377
  }
5368
5378
  async function environmentRequest(projectId, path, method, body, query) {
5369
5379
  projectId = decodeURIComponent(projectId);
5370
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/environments${path}`);
5380
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/environments${path}`);
5371
5381
  for (const [key, value] of Object.entries(query ?? {}))
5372
5382
  if (value !== undefined)
5373
5383
  url.searchParams.set(key, String(value));
@@ -5377,13 +5387,13 @@ async function environmentRequest(projectId, path, method, body, query) {
5377
5387
  }
5378
5388
  async function pipelineTriggerRequest(projectId, path = "", method = "GET", body) {
5379
5389
  projectId = decodeURIComponent(projectId);
5380
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/triggers${path}`, { ...getFetchConfig(), method, body: body === undefined ? undefined : JSON.stringify(body) });
5390
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/triggers${path}`, { ...getFetchConfig(), method, body: body === undefined ? undefined : JSON.stringify(body) });
5381
5391
  await handleGitLabError(response);
5382
5392
  return response.status === 204 ? { success: true } : response.json();
5383
5393
  }
5384
5394
  async function triggerPipeline(projectId, token, ref, variables, inputs) {
5385
5395
  projectId = decodeURIComponent(projectId);
5386
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/trigger/pipeline`);
5396
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/trigger/pipeline`);
5387
5397
  url.searchParams.set("token", token);
5388
5398
  url.searchParams.set("ref", ref);
5389
5399
  const body = {};
@@ -5405,7 +5415,7 @@ async function triggerPipeline(projectId, token, ref, variables, inputs) {
5405
5415
  */
5406
5416
  async function listPipelineJobs(projectId, pipelineId, options = {}) {
5407
5417
  projectId = decodeURIComponent(projectId); // Decode project ID
5408
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/jobs`);
5418
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/jobs`);
5409
5419
  // Add all query parameters
5410
5420
  appendFilterParams(url.searchParams, options);
5411
5421
  const response = await fetch(url.toString(), {
@@ -5428,7 +5438,7 @@ async function listPipelineJobs(projectId, pipelineId, options = {}) {
5428
5438
  */
5429
5439
  async function listPipelineTriggerJobs(projectId, pipelineId, options = {}) {
5430
5440
  projectId = decodeURIComponent(projectId); // Decode project ID
5431
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/bridges`);
5441
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/bridges`);
5432
5442
  // Add all query parameters
5433
5443
  appendFilterParams(url.searchParams, options);
5434
5444
  const response = await fetch(url.toString(), {
@@ -5443,7 +5453,7 @@ async function listPipelineTriggerJobs(projectId, pipelineId, options = {}) {
5443
5453
  }
5444
5454
  async function getPipelineJob(projectId, jobId, signal) {
5445
5455
  projectId = decodeURIComponent(projectId); // Decode project ID
5446
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}`);
5456
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}`);
5447
5457
  const response = await fetch(url.toString(), {
5448
5458
  ...getFetchConfig(),
5449
5459
  signal,
@@ -5467,7 +5477,7 @@ async function getPipelineJob(projectId, jobId, signal) {
5467
5477
  const MAX_JOB_TRACE_LINES = 1000;
5468
5478
  async function getPipelineJobOutput(projectId, jobId, limit, offset) {
5469
5479
  projectId = decodeURIComponent(projectId); // Decode project ID
5470
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/trace`);
5480
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/trace`);
5471
5481
  const response = await fetch(url.toString(), {
5472
5482
  ...getFetchConfig(),
5473
5483
  headers: {
@@ -5502,7 +5512,7 @@ async function getPipelineJobOutput(projectId, jobId, limit, offset) {
5502
5512
  }
5503
5513
  async function validateCiLint(projectId, options) {
5504
5514
  projectId = decodeURIComponent(projectId);
5505
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/ci/lint`);
5515
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/ci/lint`);
5506
5516
  const response = await fetch(url.toString(), {
5507
5517
  ...getFetchConfig(),
5508
5518
  method: "POST",
@@ -5514,7 +5524,7 @@ async function validateCiLint(projectId, options) {
5514
5524
  }
5515
5525
  async function validateProjectCiLint(projectId, options) {
5516
5526
  projectId = decodeURIComponent(projectId);
5517
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/ci/lint`);
5527
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/ci/lint`);
5518
5528
  appendFilterParams(url.searchParams, options);
5519
5529
  const response = await fetch(url.toString(), {
5520
5530
  ...getFetchConfig(),
@@ -5533,7 +5543,7 @@ async function validateProjectCiLint(projectId, options) {
5533
5543
  */
5534
5544
  async function listJobArtifacts(projectId, jobId, options = {}) {
5535
5545
  projectId = decodeURIComponent(projectId);
5536
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/artifacts/tree`);
5546
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/artifacts/tree`);
5537
5547
  appendFilterParams(url.searchParams, options);
5538
5548
  const response = await fetch(url.toString(), {
5539
5549
  ...getFetchConfig(),
@@ -5556,10 +5566,8 @@ async function listJobArtifacts(projectId, jobId, options = {}) {
5556
5566
  async function downloadJobArtifacts(projectId, jobId, localPath) {
5557
5567
  projectId = decodeURIComponent(projectId);
5558
5568
  const effectiveProjectId = getEffectiveProjectId(projectId);
5559
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/jobs/${encodeGitLabPathSegment(jobId)}/artifacts`);
5560
- const response = await fetch(url.toString(), {
5561
- ...getFetchConfig(),
5562
- });
5569
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/jobs/${encodeGitLabPathSegment(jobId)}/artifacts`);
5570
+ const response = await fetchWithValidatedRedirects(url.toString(), downloadRedirectOptions());
5563
5571
  if (response.status === 404) {
5564
5572
  throw new Error(`Job artifacts not found. The job may not have produced artifacts or the job ID is invalid.`);
5565
5573
  }
@@ -5583,14 +5591,9 @@ async function downloadJobArtifacts(projectId, jobId, localPath) {
5583
5591
  async function getJobArtifactFile(projectId, jobId, artifactPath) {
5584
5592
  projectId = decodeURIComponent(projectId);
5585
5593
  const effectiveProjectId = getEffectiveProjectId(projectId);
5586
- const encodedArtifactPath = artifactPath
5587
- .split("/")
5588
- .map(segment => encodeGitLabPathSegment(segment))
5589
- .join("/");
5590
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/jobs/${encodeGitLabPathSegment(jobId)}/artifacts/${encodedArtifactPath}`);
5591
- const response = await fetch(url.toString(), {
5592
- ...getFetchConfig(),
5593
- });
5594
+ const encodedArtifactPath = encodeGitLabPath(artifactPath);
5595
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/jobs/${encodeGitLabPathSegment(jobId)}/artifacts/${encodedArtifactPath}`);
5596
+ const response = await fetchWithValidatedRedirects(url.toString(), downloadRedirectOptions());
5594
5597
  if (response.status === 404) {
5595
5598
  throw new Error(`Artifact file not found: ${artifactPath}`);
5596
5599
  }
@@ -5608,7 +5611,7 @@ async function getJobArtifactFile(projectId, jobId, artifactPath) {
5608
5611
  */
5609
5612
  async function createPipeline(projectId, ref, variables, inputs) {
5610
5613
  projectId = decodeURIComponent(projectId); // Decode project ID
5611
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline`);
5614
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline`);
5612
5615
  const body = { ref };
5613
5616
  if (variables && variables.length > 0) {
5614
5617
  body.variables = variables;
@@ -5634,7 +5637,7 @@ async function createPipeline(projectId, ref, variables, inputs) {
5634
5637
  */
5635
5638
  async function retryPipeline(projectId, pipelineId) {
5636
5639
  projectId = decodeURIComponent(projectId); // Decode project ID
5637
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/retry`);
5640
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/retry`);
5638
5641
  const response = await fetch(url.toString(), {
5639
5642
  ...getFetchConfig(),
5640
5643
  method: "POST",
@@ -5652,7 +5655,7 @@ async function retryPipeline(projectId, pipelineId) {
5652
5655
  */
5653
5656
  async function cancelPipeline(projectId, pipelineId) {
5654
5657
  projectId = decodeURIComponent(projectId); // Decode project ID
5655
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/cancel`);
5658
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/cancel`);
5656
5659
  const response = await fetch(url.toString(), {
5657
5660
  ...getFetchConfig(),
5658
5661
  method: "POST",
@@ -5670,7 +5673,7 @@ async function cancelPipeline(projectId, pipelineId) {
5670
5673
  */
5671
5674
  async function listPipelineSchedules(projectId, options = {}) {
5672
5675
  projectId = decodeURIComponent(projectId); // Decode project ID
5673
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline_schedules`);
5676
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules`);
5674
5677
  appendFilterParams(url.searchParams, options);
5675
5678
  const response = await fetch(url.toString(), {
5676
5679
  ...getFetchConfig(),
@@ -5688,7 +5691,7 @@ async function listPipelineSchedules(projectId, options = {}) {
5688
5691
  */
5689
5692
  async function getPipelineSchedule(projectId, pipelineScheduleId) {
5690
5693
  projectId = decodeURIComponent(projectId); // Decode project ID
5691
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}`);
5694
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}`);
5692
5695
  const response = await fetch(url.toString(), {
5693
5696
  ...getFetchConfig(),
5694
5697
  });
@@ -5706,7 +5709,7 @@ async function getPipelineSchedule(projectId, pipelineScheduleId) {
5706
5709
  */
5707
5710
  async function listPipelineSchedulePipelines(projectId, pipelineScheduleId, options = {}) {
5708
5711
  projectId = decodeURIComponent(projectId); // Decode project ID
5709
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/pipelines`);
5712
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/pipelines`);
5710
5713
  appendFilterParams(url.searchParams, options);
5711
5714
  const response = await fetch(url.toString(), {
5712
5715
  ...getFetchConfig(),
@@ -5724,7 +5727,7 @@ async function listPipelineSchedulePipelines(projectId, pipelineScheduleId, opti
5724
5727
  */
5725
5728
  async function createPipelineSchedule(projectId, options) {
5726
5729
  projectId = decodeURIComponent(projectId); // Decode project ID
5727
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline_schedules`);
5730
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules`);
5728
5731
  const response = await fetch(url.toString(), {
5729
5732
  ...getFetchConfig(),
5730
5733
  method: "POST",
@@ -5744,7 +5747,7 @@ async function createPipelineSchedule(projectId, options) {
5744
5747
  */
5745
5748
  async function updatePipelineSchedule(projectId, pipelineScheduleId, options) {
5746
5749
  projectId = decodeURIComponent(projectId); // Decode project ID
5747
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}`);
5750
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}`);
5748
5751
  const response = await fetch(url.toString(), {
5749
5752
  ...getFetchConfig(),
5750
5753
  method: "PUT",
@@ -5763,7 +5766,7 @@ async function updatePipelineSchedule(projectId, pipelineScheduleId, options) {
5763
5766
  */
5764
5767
  async function deletePipelineSchedule(projectId, pipelineScheduleId) {
5765
5768
  projectId = decodeURIComponent(projectId); // Decode project ID
5766
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}`);
5769
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}`);
5767
5770
  const response = await fetch(url.toString(), {
5768
5771
  ...getFetchConfig(),
5769
5772
  method: "DELETE",
@@ -5779,7 +5782,7 @@ async function deletePipelineSchedule(projectId, pipelineScheduleId) {
5779
5782
  */
5780
5783
  async function playPipelineSchedule(projectId, pipelineScheduleId) {
5781
5784
  projectId = decodeURIComponent(projectId); // Decode project ID
5782
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/play`);
5785
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/play`);
5783
5786
  const response = await fetch(url.toString(), {
5784
5787
  ...getFetchConfig(),
5785
5788
  method: "POST",
@@ -5797,7 +5800,7 @@ async function playPipelineSchedule(projectId, pipelineScheduleId) {
5797
5800
  */
5798
5801
  async function takeOwnershipPipelineSchedule(projectId, pipelineScheduleId) {
5799
5802
  projectId = decodeURIComponent(projectId); // Decode project ID
5800
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/take_ownership`);
5803
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/take_ownership`);
5801
5804
  const response = await fetch(url.toString(), {
5802
5805
  ...getFetchConfig(),
5803
5806
  method: "POST",
@@ -5816,7 +5819,7 @@ async function takeOwnershipPipelineSchedule(projectId, pipelineScheduleId) {
5816
5819
  */
5817
5820
  async function getPipelineScheduleVariable(projectId, pipelineScheduleId, key) {
5818
5821
  projectId = decodeURIComponent(projectId); // Decode project ID
5819
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/variables/${encodeGitLabPathSegment(key)}`);
5822
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/variables/${encodeGitLabPathSegment(key)}`);
5820
5823
  const response = await fetch(url.toString(), {
5821
5824
  ...getFetchConfig(),
5822
5825
  });
@@ -5834,7 +5837,7 @@ async function getPipelineScheduleVariable(projectId, pipelineScheduleId, key) {
5834
5837
  */
5835
5838
  async function createPipelineScheduleVariable(projectId, pipelineScheduleId, options) {
5836
5839
  projectId = decodeURIComponent(projectId); // Decode project ID
5837
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/variables`);
5840
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/variables`);
5838
5841
  const response = await fetch(url.toString(), {
5839
5842
  ...getFetchConfig(),
5840
5843
  method: "POST",
@@ -5855,7 +5858,7 @@ async function createPipelineScheduleVariable(projectId, pipelineScheduleId, opt
5855
5858
  */
5856
5859
  async function updatePipelineScheduleVariable(projectId, pipelineScheduleId, key, options) {
5857
5860
  projectId = decodeURIComponent(projectId); // Decode project ID
5858
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/variables/${encodeGitLabPathSegment(key)}`);
5861
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/variables/${encodeGitLabPathSegment(key)}`);
5859
5862
  const response = await fetch(url.toString(), {
5860
5863
  ...getFetchConfig(),
5861
5864
  method: "PUT",
@@ -5875,7 +5878,7 @@ async function updatePipelineScheduleVariable(projectId, pipelineScheduleId, key
5875
5878
  */
5876
5879
  async function deletePipelineScheduleVariable(projectId, pipelineScheduleId, key) {
5877
5880
  projectId = decodeURIComponent(projectId); // Decode project ID
5878
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/variables/${encodeGitLabPathSegment(key)}`);
5881
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/variables/${encodeGitLabPathSegment(key)}`);
5879
5882
  const response = await fetch(url.toString(), {
5880
5883
  ...getFetchConfig(),
5881
5884
  method: "DELETE",
@@ -5892,7 +5895,7 @@ async function deletePipelineScheduleVariable(projectId, pipelineScheduleId, key
5892
5895
  */
5893
5896
  async function playPipelineJob(projectId, jobId, variables, jobInputs) {
5894
5897
  projectId = decodeURIComponent(projectId);
5895
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/play`);
5898
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/play`);
5896
5899
  const body = {};
5897
5900
  if (variables && variables.length > 0) {
5898
5901
  body.job_variables_attributes = variables;
@@ -5926,7 +5929,7 @@ async function playPipelineJobs(projectId, jobIds, variables, timeoutSeconds = 3
5926
5929
  */
5927
5930
  async function retryPipelineJob(projectId, jobId, jobInputs) {
5928
5931
  projectId = decodeURIComponent(projectId);
5929
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/retry`);
5932
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/retry`);
5930
5933
  const response = await fetch(url.toString(), {
5931
5934
  ...getFetchConfig(),
5932
5935
  method: "POST",
@@ -5938,7 +5941,7 @@ async function retryPipelineJob(projectId, jobId, jobInputs) {
5938
5941
  }
5939
5942
  async function erasePipelineJob(projectId, jobId) {
5940
5943
  projectId = decodeURIComponent(projectId);
5941
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/erase`, { ...getFetchConfig(), method: "POST" });
5944
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/erase`, { ...getFetchConfig(), method: "POST" });
5942
5945
  await handleGitLabError(response);
5943
5946
  return response.status === 204 ? { erased: true } : response.json();
5944
5947
  }
@@ -5974,7 +5977,7 @@ async function waitForStatus(fetchStatus, terminalStatuses, timeoutSeconds = 300
5974
5977
  */
5975
5978
  async function cancelPipelineJob(projectId, jobId, force) {
5976
5979
  projectId = decodeURIComponent(projectId);
5977
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/cancel`);
5980
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/cancel`);
5978
5981
  if (force !== undefined) {
5979
5982
  url.searchParams.append("force", force.toString());
5980
5983
  }
@@ -6006,7 +6009,7 @@ async function getRepositoryTree(options) {
6006
6009
  queryParams.append("page_token", options.page_token);
6007
6010
  if (options.pagination)
6008
6011
  queryParams.append("pagination", options.pagination);
6009
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(options.project_id))}/repository/tree?${queryParams.toString()}`, { ...getFetchConfig() });
6012
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(options.project_id))}/repository/tree?${queryParams.toString()}`, { ...getFetchConfig() });
6010
6013
  if (response.status === 404) {
6011
6014
  throw new Error("Repository or path not found");
6012
6015
  }
@@ -6028,7 +6031,7 @@ async function getRepositoryTree(options) {
6028
6031
  */
6029
6032
  async function listProjectMilestones(projectId, options) {
6030
6033
  projectId = decodeURIComponent(projectId);
6031
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones`);
6034
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones`);
6032
6035
  Object.entries(options).forEach(([key, value]) => {
6033
6036
  // Drop blank entries inside arrays so they behave like the scalar blank guard below
6034
6037
  const normalized = dropBlankArrayEntries(value);
@@ -6059,7 +6062,7 @@ async function listProjectMilestones(projectId, options) {
6059
6062
  */
6060
6063
  async function getProjectMilestone(projectId, milestoneId) {
6061
6064
  projectId = decodeURIComponent(projectId);
6062
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
6065
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
6063
6066
  const response = await fetch(url.toString(), {
6064
6067
  ...getFetchConfig(),
6065
6068
  });
@@ -6075,7 +6078,7 @@ async function getProjectMilestone(projectId, milestoneId) {
6075
6078
  */
6076
6079
  async function createProjectMilestone(projectId, options) {
6077
6080
  projectId = decodeURIComponent(projectId);
6078
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones`);
6081
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones`);
6079
6082
  const response = await fetch(url.toString(), {
6080
6083
  ...getFetchConfig(),
6081
6084
  method: "POST",
@@ -6094,7 +6097,7 @@ async function createProjectMilestone(projectId, options) {
6094
6097
  */
6095
6098
  async function editProjectMilestone(projectId, milestoneId, options) {
6096
6099
  projectId = decodeURIComponent(projectId);
6097
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
6100
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
6098
6101
  const response = await fetch(url.toString(), {
6099
6102
  ...getFetchConfig(),
6100
6103
  method: "PUT",
@@ -6112,7 +6115,7 @@ async function editProjectMilestone(projectId, milestoneId, options) {
6112
6115
  */
6113
6116
  async function deleteProjectMilestone(projectId, milestoneId) {
6114
6117
  projectId = decodeURIComponent(projectId);
6115
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
6118
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
6116
6119
  const response = await fetch(url.toString(), {
6117
6120
  ...getFetchConfig(),
6118
6121
  method: "DELETE",
@@ -6127,7 +6130,7 @@ async function deleteProjectMilestone(projectId, milestoneId) {
6127
6130
  */
6128
6131
  async function getMilestoneIssues(projectId, milestoneId, options = {}) {
6129
6132
  projectId = decodeURIComponent(projectId);
6130
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/issues`);
6133
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/issues`);
6131
6134
  appendFilterParams(url.searchParams, options);
6132
6135
  const response = await fetch(url.toString(), {
6133
6136
  ...getFetchConfig(),
@@ -6144,7 +6147,7 @@ async function getMilestoneIssues(projectId, milestoneId, options = {}) {
6144
6147
  */
6145
6148
  async function getMilestoneMergeRequests(projectId, milestoneId, options = {}) {
6146
6149
  projectId = decodeURIComponent(projectId);
6147
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/merge_requests`);
6150
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/merge_requests`);
6148
6151
  appendFilterParams(url.searchParams, options);
6149
6152
  const response = await fetch(url.toString(), {
6150
6153
  ...getFetchConfig(),
@@ -6161,7 +6164,7 @@ async function getMilestoneMergeRequests(projectId, milestoneId, options = {}) {
6161
6164
  */
6162
6165
  async function promoteProjectMilestone(projectId, milestoneId) {
6163
6166
  projectId = decodeURIComponent(projectId);
6164
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/promote`);
6167
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/promote`);
6165
6168
  const response = await fetch(url.toString(), {
6166
6169
  ...getFetchConfig(),
6167
6170
  method: "POST",
@@ -6178,7 +6181,7 @@ async function promoteProjectMilestone(projectId, milestoneId) {
6178
6181
  */
6179
6182
  async function getMilestoneBurndownEvents(projectId, milestoneId, options = {}) {
6180
6183
  projectId = decodeURIComponent(projectId);
6181
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/burndown_events`);
6184
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/burndown_events`);
6182
6185
  appendFilterParams(url.searchParams, options);
6183
6186
  const response = await fetch(url.toString(), {
6184
6187
  ...getFetchConfig(),
@@ -6192,7 +6195,7 @@ async function getMilestoneBurndownEvents(projectId, milestoneId, options = {})
6192
6195
  */
6193
6196
  async function listGroupMilestones(groupId, options) {
6194
6197
  groupId = decodeURIComponent(groupId);
6195
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/milestones`);
6198
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/milestones`);
6196
6199
  Object.entries(options).forEach(([key, value]) => {
6197
6200
  // Drop blank entries inside arrays so they behave like the scalar blank guard below
6198
6201
  const normalized = dropBlankArrayEntries(value);
@@ -6220,7 +6223,7 @@ async function listGroupMilestones(groupId, options) {
6220
6223
  */
6221
6224
  async function getGroupMilestone(groupId, milestoneId) {
6222
6225
  groupId = decodeURIComponent(groupId);
6223
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
6226
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
6224
6227
  const response = await fetch(url.toString(), {
6225
6228
  ...getFetchConfig(),
6226
6229
  });
@@ -6233,7 +6236,7 @@ async function getGroupMilestone(groupId, milestoneId) {
6233
6236
  */
6234
6237
  async function createGroupMilestone(groupId, options) {
6235
6238
  groupId = decodeURIComponent(groupId);
6236
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/milestones`);
6239
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/milestones`);
6237
6240
  const response = await fetch(url.toString(), {
6238
6241
  ...getFetchConfig(),
6239
6242
  method: "POST",
@@ -6248,7 +6251,7 @@ async function createGroupMilestone(groupId, options) {
6248
6251
  */
6249
6252
  async function editGroupMilestone(groupId, milestoneId, options) {
6250
6253
  groupId = decodeURIComponent(groupId);
6251
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
6254
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
6252
6255
  const response = await fetch(url.toString(), {
6253
6256
  ...getFetchConfig(),
6254
6257
  method: "PUT",
@@ -6263,7 +6266,7 @@ async function editGroupMilestone(groupId, milestoneId, options) {
6263
6266
  */
6264
6267
  async function deleteGroupMilestone(groupId, milestoneId) {
6265
6268
  groupId = decodeURIComponent(groupId);
6266
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
6269
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
6267
6270
  const response = await fetch(url.toString(), {
6268
6271
  ...getFetchConfig(),
6269
6272
  method: "DELETE",
@@ -6275,7 +6278,7 @@ async function deleteGroupMilestone(groupId, milestoneId) {
6275
6278
  */
6276
6279
  async function getGroupMilestoneIssues(groupId, milestoneId, options = {}) {
6277
6280
  groupId = decodeURIComponent(groupId);
6278
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}/issues`);
6281
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}/issues`);
6279
6282
  appendFilterParams(url.searchParams, options);
6280
6283
  const response = await fetch(url.toString(), {
6281
6284
  ...getFetchConfig(),
@@ -6289,7 +6292,7 @@ async function getGroupMilestoneIssues(groupId, milestoneId, options = {}) {
6289
6292
  */
6290
6293
  async function getGroupMilestoneMergeRequests(groupId, milestoneId, options = {}) {
6291
6294
  groupId = decodeURIComponent(groupId);
6292
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}/merge_requests`);
6295
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}/merge_requests`);
6293
6296
  appendFilterParams(url.searchParams, options);
6294
6297
  const response = await fetch(url.toString(), {
6295
6298
  ...getFetchConfig(),
@@ -6303,7 +6306,7 @@ async function getGroupMilestoneMergeRequests(groupId, milestoneId, options = {}
6303
6306
  */
6304
6307
  async function getGroupMilestoneBurndownEvents(groupId, milestoneId, options = {}) {
6305
6308
  groupId = decodeURIComponent(groupId);
6306
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}/burndown_events`);
6309
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}/burndown_events`);
6307
6310
  appendFilterParams(url.searchParams, options);
6308
6311
  const response = await fetch(url.toString(), {
6309
6312
  ...getFetchConfig(),
@@ -6374,7 +6377,7 @@ async function getUsers(usernames) {
6374
6377
  */
6375
6378
  async function listCommits(projectId, options = {}) {
6376
6379
  projectId = decodeURIComponent(projectId);
6377
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits`);
6380
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/commits`);
6378
6381
  // Add query parameters
6379
6382
  if (options.ref_name)
6380
6383
  url.searchParams.append("ref_name", options.ref_name);
@@ -6418,7 +6421,7 @@ async function listCommits(projectId, options = {}) {
6418
6421
  */
6419
6422
  async function getCommit(projectId, sha, stats) {
6420
6423
  projectId = decodeURIComponent(projectId);
6421
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits/${encodeURIComponent(sha)}`);
6424
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/commits/${encodeGitLabPathSegment(sha)}`);
6422
6425
  if (stats) {
6423
6426
  url.searchParams.append("stats", "true");
6424
6427
  }
@@ -6440,7 +6443,7 @@ async function getCommit(projectId, sha, stats) {
6440
6443
  */
6441
6444
  async function getCommitDiff(projectId, sha, full_diff) {
6442
6445
  projectId = decodeURIComponent(projectId);
6443
- const baseUrl = `${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits/${encodeURIComponent(sha)}/diff`;
6446
+ const baseUrl = `${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/commits/${encodeGitLabPathSegment(sha)}/diff`;
6444
6447
  let allDiffs = [];
6445
6448
  let page = 1;
6446
6449
  while (true) {
@@ -6479,7 +6482,7 @@ async function getCommitDiff(projectId, sha, full_diff) {
6479
6482
  */
6480
6483
  async function getFileBlame(projectId, options) {
6481
6484
  projectId = decodeURIComponent(projectId);
6482
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/files/${encodeURIComponent(options.file_path)}/blame`);
6485
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/files/${encodeGitLabPathSegment(options.file_path)}/blame`);
6483
6486
  url.searchParams.append("ref", options.ref);
6484
6487
  if (options.range_start !== undefined && options.range_end !== undefined) {
6485
6488
  url.searchParams.append("range[start]", options.range_start.toString());
@@ -6502,7 +6505,7 @@ async function getFileBlame(projectId, options) {
6502
6505
  */
6503
6506
  async function listCommitStatuses(projectId, sha, options = {}) {
6504
6507
  projectId = decodeURIComponent(projectId);
6505
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits/${encodeURIComponent(sha)}/statuses`);
6508
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/commits/${encodeGitLabPathSegment(sha)}/statuses`);
6506
6509
  appendFilterParams(url.searchParams, options);
6507
6510
  const response = await fetch(url.toString(), {
6508
6511
  ...getFetchConfig(),
@@ -6524,7 +6527,7 @@ async function createCommitStatus(projectId, sha, options) {
6524
6527
  throw new Error("Use either name or context when creating a commit status, not both.");
6525
6528
  }
6526
6529
  projectId = decodeURIComponent(projectId);
6527
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/statuses/${encodeURIComponent(sha)}`);
6530
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/statuses/${encodeGitLabPathSegment(sha)}`);
6528
6531
  appendFilterParams(url.searchParams, options);
6529
6532
  const response = await fetch(url.toString(), {
6530
6533
  ...getFetchConfig(),
@@ -6614,7 +6617,7 @@ async function listProjectMembers(projectId, options = {}) {
6614
6617
  projectId = decodeURIComponent(projectId);
6615
6618
  const effectiveProjectId = getEffectiveProjectId(projectId);
6616
6619
  const membersPath = options.include_inheritance ? "members/all" : "members";
6617
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/${membersPath}`);
6620
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/${membersPath}`);
6618
6621
  // Add query parameters
6619
6622
  if (options.query)
6620
6623
  url.searchParams.append("query", options.query);
@@ -6644,7 +6647,7 @@ async function listProjectMembers(projectId, options = {}) {
6644
6647
  async function listGroupMembers(groupId, options = {}) {
6645
6648
  groupId = decodeURIComponent(groupId);
6646
6649
  const membersPath = options.include_inheritance ? "members/all" : "members";
6647
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/${membersPath}`);
6650
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/${membersPath}`);
6648
6651
  if (options.query)
6649
6652
  url.searchParams.append("query", options.query);
6650
6653
  if (options.user_ids) {
@@ -6671,7 +6674,7 @@ async function listGroupMembers(groupId, options = {}) {
6671
6674
  */
6672
6675
  async function listGroupIterations(groupId, options = {}) {
6673
6676
  groupId = decodeURIComponent(groupId);
6674
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/iterations`);
6677
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/iterations`);
6675
6678
  // クエリパラメータの追加
6676
6679
  if (options.state)
6677
6680
  url.searchParams.append("state", options.state);
@@ -6701,7 +6704,7 @@ async function listGroupIterations(groupId, options = {}) {
6701
6704
  // --- CI/CD Variables ---
6702
6705
  async function listProjectVariables(projectId, options = {}) {
6703
6706
  projectId = decodeURIComponent(projectId);
6704
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/variables`);
6707
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/variables`);
6705
6708
  Object.entries(options).forEach(([key, value]) => {
6706
6709
  if (key === "filter" && typeof value === "object" && value !== null) {
6707
6710
  Object.entries(value).forEach(([filterKey, filterValue]) => {
@@ -6718,7 +6721,7 @@ async function listProjectVariables(projectId, options = {}) {
6718
6721
  }
6719
6722
  async function getProjectVariable(projectId, key, filter) {
6720
6723
  projectId = decodeURIComponent(projectId);
6721
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/variables/${encodeURIComponent(key)}`);
6724
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/variables/${encodeGitLabPathSegment(key)}`);
6722
6725
  if (filter?.environment_scope) {
6723
6726
  url.searchParams.append("filter[environment_scope]", filter.environment_scope);
6724
6727
  }
@@ -6729,7 +6732,7 @@ async function getProjectVariable(projectId, key, filter) {
6729
6732
  }
6730
6733
  async function createProjectVariable(projectId, options) {
6731
6734
  projectId = decodeURIComponent(projectId);
6732
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/variables`, { ...getFetchConfig(), method: "POST", body: JSON.stringify(options) });
6735
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/variables`, { ...getFetchConfig(), method: "POST", body: JSON.stringify(options) });
6733
6736
  await handleGitLabError(response);
6734
6737
  const data = await response.json();
6735
6738
  return GitLabCiVariableSchema.parse(data);
@@ -6737,7 +6740,7 @@ async function createProjectVariable(projectId, options) {
6737
6740
  async function updateProjectVariable(projectId, key, options) {
6738
6741
  projectId = decodeURIComponent(projectId);
6739
6742
  const { filter, ...body } = options;
6740
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/variables/${encodeURIComponent(key)}`);
6743
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/variables/${encodeGitLabPathSegment(key)}`);
6741
6744
  if (filter?.environment_scope) {
6742
6745
  url.searchParams.append("filter[environment_scope]", filter.environment_scope);
6743
6746
  }
@@ -6752,7 +6755,7 @@ async function updateProjectVariable(projectId, key, options) {
6752
6755
  }
6753
6756
  async function deleteProjectVariable(projectId, key, filter) {
6754
6757
  projectId = decodeURIComponent(projectId);
6755
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/variables/${encodeURIComponent(key)}`);
6758
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/variables/${encodeGitLabPathSegment(key)}`);
6756
6759
  if (filter?.environment_scope) {
6757
6760
  url.searchParams.append("filter[environment_scope]", filter.environment_scope);
6758
6761
  }
@@ -6760,7 +6763,7 @@ async function deleteProjectVariable(projectId, key, filter) {
6760
6763
  await handleGitLabError(response);
6761
6764
  }
6762
6765
  async function listGroupVariables(groupId, options = {}) {
6763
- const encoded = encodeURIComponent(decodeURIComponent(groupId));
6766
+ const encoded = encodeGitLabPathSegment(groupId);
6764
6767
  const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables`);
6765
6768
  Object.entries(options).forEach(([key, value]) => {
6766
6769
  if (key === "filter" && typeof value === "object" && value !== null) {
@@ -6777,8 +6780,8 @@ async function listGroupVariables(groupId, options = {}) {
6777
6780
  return z.array(GitLabCiVariableSchema).parse(data);
6778
6781
  }
6779
6782
  async function getGroupVariable(groupId, key, filter) {
6780
- const encoded = encodeURIComponent(decodeURIComponent(groupId));
6781
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${encodeURIComponent(key)}`);
6783
+ const encoded = encodeGitLabPathSegment(groupId);
6784
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${encodeGitLabPathSegment(key)}`);
6782
6785
  if (filter?.environment_scope) {
6783
6786
  url.searchParams.append("filter[environment_scope]", filter.environment_scope);
6784
6787
  }
@@ -6788,7 +6791,7 @@ async function getGroupVariable(groupId, key, filter) {
6788
6791
  return GitLabCiVariableSchema.parse(data);
6789
6792
  }
6790
6793
  async function createGroupVariable(groupId, options) {
6791
- const encoded = encodeURIComponent(decodeURIComponent(groupId));
6794
+ const encoded = encodeGitLabPathSegment(groupId);
6792
6795
  const response = await fetch(`${getEffectiveApiUrl()}/groups/${encoded}/variables`, {
6793
6796
  ...getFetchConfig(),
6794
6797
  method: "POST",
@@ -6799,9 +6802,9 @@ async function createGroupVariable(groupId, options) {
6799
6802
  return GitLabCiVariableSchema.parse(data);
6800
6803
  }
6801
6804
  async function updateGroupVariable(groupId, key, options) {
6802
- const encoded = encodeURIComponent(decodeURIComponent(groupId));
6805
+ const encoded = encodeGitLabPathSegment(groupId);
6803
6806
  const { filter, ...body } = options;
6804
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${encodeURIComponent(key)}`);
6807
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${encodeGitLabPathSegment(key)}`);
6805
6808
  if (filter?.environment_scope) {
6806
6809
  url.searchParams.append("filter[environment_scope]", filter.environment_scope);
6807
6810
  }
@@ -6815,8 +6818,8 @@ async function updateGroupVariable(groupId, key, options) {
6815
6818
  return GitLabCiVariableSchema.parse(data);
6816
6819
  }
6817
6820
  async function deleteGroupVariable(groupId, key, filter) {
6818
- const encoded = encodeURIComponent(decodeURIComponent(groupId));
6819
- const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${encodeURIComponent(key)}`);
6821
+ const encoded = encodeGitLabPathSegment(groupId);
6822
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${encodeGitLabPathSegment(key)}`);
6820
6823
  if (filter?.environment_scope) {
6821
6824
  url.searchParams.append("filter[environment_scope]", filter.environment_scope);
6822
6825
  }
@@ -6902,7 +6905,7 @@ async function listDependencyProxyBlobs(groupPath, options = {}) {
6902
6905
  };
6903
6906
  }
6904
6907
  async function purgeDependencyProxyCache(groupId) {
6905
- const encoded = encodeURIComponent(decodeURIComponent(groupId));
6908
+ const encoded = encodeGitLabPathSegment(groupId);
6906
6909
  const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/dependency_proxy/cache`);
6907
6910
  const response = await fetch(url.toString(), { ...getFetchConfig(), method: "DELETE" });
6908
6911
  await handleGitLabError(response);
@@ -6932,7 +6935,7 @@ async function resolveProjectFullPath(projectId) {
6932
6935
  if (!/^\d+$/.test(decoded)) {
6933
6936
  return decoded;
6934
6937
  }
6935
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(decoded)}`);
6938
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(decoded)}`);
6936
6939
  const response = await fetch(url.toString(), { ...getFetchConfig() });
6937
6940
  await handleGitLabError(response);
6938
6941
  const project = (await response.json());
@@ -7243,7 +7246,7 @@ async function markdownUpload(projectId, filePath, content, filename) {
7243
7246
  // Create form data
7244
7247
  const form = new FormData();
7245
7248
  form.append("file", new File([fileBuffer], fileName, { type: "application/octet-stream" }));
7246
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/uploads`);
7249
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/uploads`);
7247
7250
  const defaultFetchConfig = getFetchConfig();
7248
7251
  delete defaultFetchConfig.headers["Content-Type"];
7249
7252
  const response = await fetch(url.toString(), {
@@ -7284,11 +7287,8 @@ function getSafeAttachmentFilename(filename) {
7284
7287
  async function downloadAttachment(projectId, secret, filename, localPath) {
7285
7288
  const effectiveProjectId = getEffectiveProjectId(projectId);
7286
7289
  const safeFilename = getSafeAttachmentFilename(filename);
7287
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/uploads/${encodeGitLabPathSegment(secret)}/${encodeGitLabPathSegment(safeFilename)}`);
7288
- const response = await fetch(url.toString(), {
7289
- ...getFetchConfig(),
7290
- method: "GET",
7291
- });
7290
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/uploads/${encodeGitLabPathSegment(secret)}/${encodeGitLabPathSegment(safeFilename)}`);
7291
+ const response = await fetchWithValidatedRedirects(url.toString(), downloadRedirectOptions());
7292
7292
  if (!response.ok) {
7293
7293
  await handleGitLabError(response);
7294
7294
  }
@@ -7332,7 +7332,7 @@ async function listEvents(options = {}) {
7332
7332
  */
7333
7333
  async function getProjectEvents(projectId, options = {}) {
7334
7334
  const effectiveProjectId = getEffectiveProjectId(projectId);
7335
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/events`);
7335
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/events`);
7336
7336
  // Add all query parameters
7337
7337
  appendFilterParams(url.searchParams, options);
7338
7338
  const response = await fetch(url.toString(), { ...getFetchConfig() });
@@ -7351,7 +7351,7 @@ async function getProjectEvents(projectId, options = {}) {
7351
7351
  */
7352
7352
  async function listReleases(projectId, options = {}) {
7353
7353
  const effectiveProjectId = getEffectiveProjectId(projectId);
7354
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/releases`);
7354
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/releases`);
7355
7355
  // Add query parameters
7356
7356
  appendFilterParams(url.searchParams, options);
7357
7357
  const response = await fetch(url.toString(), {
@@ -7371,7 +7371,7 @@ async function listReleases(projectId, options = {}) {
7371
7371
  */
7372
7372
  async function getRelease(projectId, tagName, includeHtmlDescription) {
7373
7373
  const effectiveProjectId = getEffectiveProjectId(projectId);
7374
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/releases/${encodeGitLabPathSegment(tagName)}`);
7374
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/releases/${encodeGitLabPathSegment(tagName)}`);
7375
7375
  if (includeHtmlDescription !== undefined) {
7376
7376
  url.searchParams.append("include_html_description", includeHtmlDescription.toString());
7377
7377
  }
@@ -7391,7 +7391,7 @@ async function getRelease(projectId, tagName, includeHtmlDescription) {
7391
7391
  */
7392
7392
  async function createRelease(projectId, options) {
7393
7393
  const effectiveProjectId = getEffectiveProjectId(projectId);
7394
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/releases`, {
7394
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/releases`, {
7395
7395
  ...getFetchConfig(),
7396
7396
  method: "POST",
7397
7397
  body: JSON.stringify(options),
@@ -7410,7 +7410,7 @@ async function createRelease(projectId, options) {
7410
7410
  */
7411
7411
  async function updateRelease(projectId, tagName, options) {
7412
7412
  const effectiveProjectId = getEffectiveProjectId(projectId);
7413
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/releases/${encodeGitLabPathSegment(tagName)}`, {
7413
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/releases/${encodeGitLabPathSegment(tagName)}`, {
7414
7414
  ...getFetchConfig(),
7415
7415
  method: "PUT",
7416
7416
  body: JSON.stringify(options),
@@ -7428,7 +7428,7 @@ async function updateRelease(projectId, tagName, options) {
7428
7428
  */
7429
7429
  async function deleteRelease(projectId, tagName) {
7430
7430
  const effectiveProjectId = getEffectiveProjectId(projectId);
7431
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/releases/${encodeGitLabPathSegment(tagName)}`, {
7431
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/releases/${encodeGitLabPathSegment(tagName)}`, {
7432
7432
  ...getFetchConfig(),
7433
7433
  method: "DELETE",
7434
7434
  });
@@ -7444,7 +7444,7 @@ async function deleteRelease(projectId, tagName) {
7444
7444
  */
7445
7445
  async function createReleaseEvidence(projectId, tagName) {
7446
7446
  const effectiveProjectId = getEffectiveProjectId(projectId);
7447
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/releases/${encodeGitLabPathSegment(tagName)}/evidence`, {
7447
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/releases/${encodeGitLabPathSegment(tagName)}/evidence`, {
7448
7448
  ...getFetchConfig(),
7449
7449
  method: "POST",
7450
7450
  });
@@ -7460,9 +7460,7 @@ async function createReleaseEvidence(projectId, tagName) {
7460
7460
  */
7461
7461
  async function downloadReleaseAsset(projectId, tagName, directAssetPath) {
7462
7462
  const effectiveProjectId = getEffectiveProjectId(projectId);
7463
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/releases/${encodeGitLabPathSegment(tagName)}/downloads/${encodeGitLabPath(directAssetPath)}`, {
7464
- ...getFetchConfig(),
7465
- });
7463
+ const response = await fetchWithValidatedRedirects(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/releases/${encodeGitLabPathSegment(tagName)}/downloads/${encodeGitLabPath(directAssetPath)}`, downloadRedirectOptions());
7466
7464
  await handleGitLabError(response);
7467
7465
  return await response.text();
7468
7466
  }
@@ -7475,7 +7473,7 @@ async function downloadReleaseAsset(projectId, tagName, directAssetPath) {
7475
7473
  */
7476
7474
  async function listTags(projectId, options = {}) {
7477
7475
  const effectiveProjectId = getEffectiveProjectId(projectId);
7478
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/repository/tags`);
7476
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/repository/tags`);
7479
7477
  appendFilterParams(url.searchParams, options);
7480
7478
  const response = await fetch(url.toString(), {
7481
7479
  ...getFetchConfig(),
@@ -7493,7 +7491,7 @@ async function listTags(projectId, options = {}) {
7493
7491
  */
7494
7492
  async function getTag(projectId, tagName) {
7495
7493
  const effectiveProjectId = getEffectiveProjectId(projectId);
7496
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/repository/tags/${encodeURIComponent(tagName)}`, {
7494
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/repository/tags/${encodeGitLabPathSegment(tagName)}`, {
7497
7495
  ...getFetchConfig(),
7498
7496
  });
7499
7497
  await handleGitLabError(response);
@@ -7509,7 +7507,7 @@ async function getTag(projectId, tagName) {
7509
7507
  */
7510
7508
  async function createTag(projectId, options) {
7511
7509
  const effectiveProjectId = getEffectiveProjectId(projectId);
7512
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/repository/tags`, {
7510
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/repository/tags`, {
7513
7511
  ...getFetchConfig(),
7514
7512
  method: "POST",
7515
7513
  body: JSON.stringify(options),
@@ -7526,7 +7524,7 @@ async function createTag(projectId, options) {
7526
7524
  */
7527
7525
  async function deleteTag(projectId, tagName) {
7528
7526
  const effectiveProjectId = getEffectiveProjectId(projectId);
7529
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/repository/tags/${encodeURIComponent(tagName)}`, {
7527
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/repository/tags/${encodeGitLabPathSegment(tagName)}`, {
7530
7528
  ...getFetchConfig(),
7531
7529
  method: "DELETE",
7532
7530
  });
@@ -7541,7 +7539,7 @@ async function deleteTag(projectId, tagName) {
7541
7539
  */
7542
7540
  async function getTagSignature(projectId, tagName) {
7543
7541
  const effectiveProjectId = getEffectiveProjectId(projectId);
7544
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/repository/tags/${encodeURIComponent(tagName)}/signature`, {
7542
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/repository/tags/${encodeGitLabPathSegment(tagName)}/signature`, {
7545
7543
  ...getFetchConfig(),
7546
7544
  });
7547
7545
  await handleGitLabError(response);
@@ -7605,7 +7603,7 @@ async function handleToolCall(params) {
7605
7603
  if (!isToolAllowedByPermissionMode(params.name)) {
7606
7604
  throw new Error(GITLAB_PERMISSION_MODE === "readonly"
7607
7605
  ? `${params.name} is not allowed in read-only mode`
7608
- : `${params.name} is not allowed in modify mode (delete operations are disabled)`);
7606
+ : `${params.name} is not allowed in modify mode (delete and teardown operations are disabled)`);
7609
7607
  }
7610
7608
  logger.info({ tool: params.name, event: "tool_call_start" }, `tool_call_start: ${params.name}`);
7611
7609
  switch (params.name) {
@@ -7618,7 +7616,7 @@ async function handleToolCall(params) {
7618
7616
  }
7619
7617
  if (GITLAB_PERMISSION_MODE === "modify" &&
7620
7618
  graphqlQueryContainsDeleteOperation(args.query)) {
7621
- throw new Error("execute_graphql does not allow delete mutations in modify mode");
7619
+ throw new Error("execute_graphql does not allow destructive mutations in modify mode");
7622
7620
  }
7623
7621
  const apiUrl = new URL(getEffectiveApiUrl());
7624
7622
  // Build GraphQL endpoint preserving any instance subpath (e.g. /gitlab)
@@ -8208,7 +8206,7 @@ async function handleToolCall(params) {
8208
8206
  }
8209
8207
  case "get_namespace": {
8210
8208
  const args = GetNamespaceSchema.parse(params.arguments);
8211
- const url = new URL(`${GITLAB_API_URL}/namespaces/${encodeURIComponent(args.namespace_id)}`);
8209
+ const url = new URL(`${GITLAB_API_URL}/namespaces/${encodeGitLabPathSegment(args.namespace_id)}`);
8212
8210
  const response = await fetch(url.toString(), {
8213
8211
  ...getFetchConfig(),
8214
8212
  });
@@ -8221,7 +8219,7 @@ async function handleToolCall(params) {
8221
8219
  }
8222
8220
  case "verify_namespace": {
8223
8221
  const args = VerifyNamespaceSchema.parse(params.arguments);
8224
- const url = new URL(`${GITLAB_API_URL}/namespaces/${encodeURIComponent(args.path)}/exists`);
8222
+ const url = new URL(`${GITLAB_API_URL}/namespaces/${encodeGitLabPathSegment(args.path)}/exists`);
8225
8223
  if (args.parent_id !== undefined)
8226
8224
  url.searchParams.set("parent_id", String(args.parent_id));
8227
8225
  const response = await fetch(url.toString(), {
@@ -8238,7 +8236,7 @@ async function handleToolCall(params) {
8238
8236
  const args = GetProjectSchema.parse(params.arguments);
8239
8237
  const options = params.arguments;
8240
8238
  const effectiveProjectId = getEffectiveProjectId(args.project_id);
8241
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}`);
8239
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}`);
8242
8240
  if (options.license)
8243
8241
  url.searchParams.append("license", "true");
8244
8242
  if (options.statistics)
@@ -8267,7 +8265,7 @@ async function handleToolCall(params) {
8267
8265
  const { project_id, ...updates } = UpdateProjectSchema.parse(params.arguments);
8268
8266
  const effectiveProjectId = getEffectiveProjectId(project_id);
8269
8267
  const body = Object.fromEntries(Object.entries(updates).filter(([, value]) => value !== undefined));
8270
- const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}`, {
8268
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}`, {
8271
8269
  ...getFetchConfig(),
8272
8270
  method: "PUT",
8273
8271
  body: JSON.stringify(body),
@@ -8306,7 +8304,7 @@ async function handleToolCall(params) {
8306
8304
  }
8307
8305
  case "get_user": {
8308
8306
  const args = GetUserSchema.parse(params.arguments);
8309
- const url = new URL(`${getEffectiveApiUrl()}/users/${encodeURIComponent(args.user_id)}`);
8307
+ const url = new URL(`${getEffectiveApiUrl()}/users/${encodeGitLabPathSegment(args.user_id)}`);
8310
8308
  const response = await fetch(url.toString(), {
8311
8309
  ...getFetchConfig(),
8312
8310
  });
@@ -10186,7 +10184,7 @@ async function handleToolCall(params) {
10186
10184
  case "get_branch": {
10187
10185
  const args = GetBranchSchema.parse(params.arguments);
10188
10186
  const projectId = decodeURIComponent(args.project_id);
10189
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/branches/${encodeURIComponent(args.branch_name)}`);
10187
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/branches/${encodeGitLabPathSegment(args.branch_name)}`);
10190
10188
  const response = await fetch(url.toString(), {
10191
10189
  ...getFetchConfig(),
10192
10190
  });
@@ -10200,7 +10198,7 @@ async function handleToolCall(params) {
10200
10198
  case "list_branches": {
10201
10199
  const args = ListBranchesSchema.parse(params.arguments);
10202
10200
  const projectId = decodeURIComponent(args.project_id);
10203
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/branches`);
10201
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/branches`);
10204
10202
  if (args.search) {
10205
10203
  url.searchParams.append("search", args.search);
10206
10204
  }
@@ -10223,7 +10221,7 @@ async function handleToolCall(params) {
10223
10221
  case "delete_branch": {
10224
10222
  const args = DeleteBranchSchema.parse(params.arguments);
10225
10223
  const projectId = decodeURIComponent(args.project_id);
10226
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/branches/${encodeURIComponent(args.branch_name)}`);
10224
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/branches/${encodeGitLabPathSegment(args.branch_name)}`);
10227
10225
  const response = await fetch(url.toString(), {
10228
10226
  ...getFetchConfig(),
10229
10227
  method: "DELETE",
@@ -10237,7 +10235,7 @@ async function handleToolCall(params) {
10237
10235
  const args = ListProtectedBranchesSchema.parse(params.arguments);
10238
10236
  const projectId = decodeURIComponent(args.project_id);
10239
10237
  const effectiveProjectId = getEffectiveProjectId(projectId);
10240
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/protected_branches`);
10238
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/protected_branches`);
10241
10239
  if (args.search)
10242
10240
  url.searchParams.append("search", args.search);
10243
10241
  if (args.page)
@@ -10257,7 +10255,7 @@ async function handleToolCall(params) {
10257
10255
  const args = GetProtectedBranchSchema.parse(params.arguments);
10258
10256
  const projectId = decodeURIComponent(args.project_id);
10259
10257
  const effectiveProjectId = getEffectiveProjectId(projectId);
10260
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/protected_branches/${encodeURIComponent(args.branch_name)}`);
10258
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/protected_branches/${encodeGitLabPathSegment(args.branch_name)}`);
10261
10259
  const response = await fetch(url.toString(), {
10262
10260
  ...getFetchConfig(),
10263
10261
  });
@@ -10271,7 +10269,7 @@ async function handleToolCall(params) {
10271
10269
  const args = ProtectBranchSchema.parse(params.arguments);
10272
10270
  const projectId = decodeURIComponent(args.project_id);
10273
10271
  const effectiveProjectId = getEffectiveProjectId(projectId);
10274
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/protected_branches`);
10272
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/protected_branches`);
10275
10273
  const body = { name: args.branch_name };
10276
10274
  if (args.push_access_level !== undefined)
10277
10275
  body.push_access_level = args.push_access_level;
@@ -10298,7 +10296,7 @@ async function handleToolCall(params) {
10298
10296
  const args = UnprotectBranchSchema.parse(params.arguments);
10299
10297
  const projectId = decodeURIComponent(args.project_id);
10300
10298
  const effectiveProjectId = getEffectiveProjectId(projectId);
10301
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/protected_branches/${encodeURIComponent(args.branch_name)}`);
10299
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/protected_branches/${encodeGitLabPathSegment(args.branch_name)}`);
10302
10300
  const response = await fetch(url.toString(), {
10303
10301
  ...getFetchConfig(),
10304
10302
  method: "DELETE",
@@ -10312,7 +10310,7 @@ async function handleToolCall(params) {
10312
10310
  const args = UpdateDefaultBranchSchema.parse(params.arguments);
10313
10311
  const projectId = decodeURIComponent(args.project_id);
10314
10312
  const effectiveProjectId = getEffectiveProjectId(projectId);
10315
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}`);
10313
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}`);
10316
10314
  const response = await fetch(url.toString(), {
10317
10315
  ...getFetchConfig(),
10318
10316
  method: "PUT",
@@ -10369,6 +10367,7 @@ function buildDownloadProxyDeps() {
10369
10367
  getDispatcherForUrl: clientPool.getDispatcherForUrl.bind(clientPool),
10370
10368
  fetch: undiciFetch,
10371
10369
  logger,
10370
+ isTrustedRedirectHost: isTrustedGitLabRedirectHost,
10372
10371
  };
10373
10372
  }
10374
10373
  /**
@@ -10430,9 +10429,68 @@ async function startSSEServer() {
10430
10429
  }
10431
10430
  next();
10432
10431
  };
10433
- const transports = {};
10432
+ // Session IDs are untrusted map keys (POST /messages?sessionId=). A null
10433
+ // prototype prevents inherited names such as "constructor" from masquerading
10434
+ // as live transports, matching the Streamable HTTP transport.
10435
+ const transports = Object.create(null);
10436
+ const sessionLastActivity = new Map();
10434
10437
  let shuttingDown = false;
10435
- app.get("/sse", rejectDnsRebinding, requireSseAuth, async (req, res) => {
10438
+ // Mirror the Streamable HTTP controls so the SSE transport cannot be used to
10439
+ // exhaust the process: a session cap, a per-IP creation rate limit and an idle
10440
+ // timeout that closes connections which never send anything.
10441
+ const maxSessions = Number.parseInt(process.env.MAX_SESSIONS || "1000", 10);
10442
+ const maxSseConnectionsPerMinute = Number.parseInt(process.env.MAX_REQUESTS_PER_MINUTE || "60", 10);
10443
+ const idleTimeoutMs = Math.max(SESSION_TIMEOUT_SECONDS, 1) * 1000;
10444
+ const sseConnectionRateLimit = rateLimit({
10445
+ windowMs: 60_000,
10446
+ limit: maxSseConnectionsPerMinute,
10447
+ keyGenerator: (req) => ipKeyGenerator(normalizeProxyClientIpForRateLimit(req.ip ?? "")),
10448
+ standardHeaders: true,
10449
+ legacyHeaders: false,
10450
+ handler: (_req, res) => {
10451
+ res.status(429).json({
10452
+ error: "Rate limit exceeded",
10453
+ message: `Maximum ${maxSseConnectionsPerMinute} SSE connections per minute allowed`,
10454
+ });
10455
+ },
10456
+ });
10457
+ /**
10458
+ * Reclaim SSE sessions that have not received a POST /messages request within
10459
+ * the idle timeout.
10460
+ *
10461
+ * On /sse it is correct to idle-close an open GET: the stream alone is not
10462
+ * proof of a live client, so a client that connects and never sends anything
10463
+ * would otherwise hold a MAX_SESSIONS slot forever. This intentionally
10464
+ * differs from the Streamable HTTP GET /mcp handler, which pauses inactivity
10465
+ * expiry while its stream is open so list_changed can still be pushed - do
10466
+ * not copy that rule here.
10467
+ */
10468
+ const closeIdleSseSessions = () => {
10469
+ const now = Date.now();
10470
+ for (const [sessionId, lastActivity] of sessionLastActivity) {
10471
+ if (now - lastActivity < idleTimeoutMs)
10472
+ continue;
10473
+ const transport = transports[sessionId];
10474
+ delete transports[sessionId];
10475
+ sessionLastActivity.delete(sessionId);
10476
+ logger.info({ sessionId, event: "sse_session_idle_closed" }, `SSE session ${sessionId} closed after ${SESSION_TIMEOUT_SECONDS}s of inactivity`);
10477
+ if (transport) {
10478
+ void transport.close().catch(error => {
10479
+ logger.error({ err: error }, "Error closing idle SSE transport");
10480
+ });
10481
+ }
10482
+ }
10483
+ };
10484
+ const idleSweep = setInterval(closeIdleSseSessions, Math.min(idleTimeoutMs, 60_000));
10485
+ idleSweep.unref();
10486
+ app.get("/sse", rejectDnsRebinding, requireSseAuth, sseConnectionRateLimit, async (req, res) => {
10487
+ if (Object.keys(transports).length >= maxSessions) {
10488
+ res.status(503).json({
10489
+ error: "Server capacity reached",
10490
+ message: `Maximum ${maxSessions} concurrent sessions allowed. Please try again later.`,
10491
+ });
10492
+ return;
10493
+ }
10436
10494
  const serverInstance = createServer();
10437
10495
  const effectiveHosts = getEffectiveAllowedHosts(req);
10438
10496
  const transport = new SSEServerTransport("/messages", res, {
@@ -10440,16 +10498,38 @@ async function startSSEServer() {
10440
10498
  allowedHosts: [...effectiveHosts],
10441
10499
  allowedOrigins: [...effectiveHosts].flatMap(host => [`http://${host}`, `https://${host}`]),
10442
10500
  });
10443
- transports[transport.sessionId] = transport;
10501
+ const sessionId = transport.sessionId;
10502
+ transports[sessionId] = transport;
10503
+ sessionLastActivity.set(sessionId, Date.now());
10444
10504
  res.on("close", () => {
10445
- delete transports[transport.sessionId];
10505
+ delete transports[sessionId];
10506
+ sessionLastActivity.delete(sessionId);
10446
10507
  });
10447
- await serverInstance.connect(transport);
10508
+ try {
10509
+ await serverInstance.connect(transport);
10510
+ }
10511
+ catch (error) {
10512
+ logger.error({ err: error, sessionId }, "SSE connection error");
10513
+ // Release the capacity slot immediately instead of waiting for the
10514
+ // client to disconnect or the idle sweep to run.
10515
+ delete transports[sessionId];
10516
+ sessionLastActivity.delete(sessionId);
10517
+ if (!res.headersSent) {
10518
+ res.status(500).json({
10519
+ error: "Internal server error",
10520
+ message: error instanceof Error ? error.message : "Unknown error",
10521
+ });
10522
+ }
10523
+ else {
10524
+ res.end();
10525
+ }
10526
+ }
10448
10527
  });
10449
10528
  app.post("/messages", rejectDnsRebinding, requireSseAuth, async (req, res) => {
10450
10529
  const sessionId = req.query.sessionId;
10451
10530
  const transport = transports[sessionId];
10452
10531
  if (transport) {
10532
+ sessionLastActivity.set(sessionId, Date.now());
10453
10533
  await transport.handlePostMessage(req, res);
10454
10534
  }
10455
10535
  else {
@@ -10458,10 +10538,18 @@ async function startSSEServer() {
10458
10538
  });
10459
10539
  registerDownloadProxy(app, buildDownloadProxyDeps());
10460
10540
  app.get("/health", (_, res) => {
10461
- res.status(200).json({
10462
- status: "healthy",
10541
+ const activeSessions = Object.keys(transports).length;
10542
+ const isHealthy = activeSessions < maxSessions;
10543
+ if (!isHealthy) {
10544
+ logger.warn({ activeSessions, maxSessions }, "Health check degraded: active SSE session capacity reached");
10545
+ }
10546
+ res.status(isHealthy ? 200 : 503).json({
10547
+ status: isHealthy ? "healthy" : "degraded",
10463
10548
  version: SERVER_VERSION,
10464
10549
  transport: TransportMode.SSE,
10550
+ activeSessions,
10551
+ maxSessions,
10552
+ uptime: process.uptime(),
10465
10553
  });
10466
10554
  });
10467
10555
  const httpServer = app.listen(Number(PORT), HOST, () => {
@@ -10473,6 +10561,7 @@ async function startSSEServer() {
10473
10561
  return;
10474
10562
  shuttingDown = true;
10475
10563
  logger.info(`${signal} received, shutting down SSE server...`);
10564
+ clearInterval(idleSweep);
10476
10565
  httpServer.close(() => logger.info("SSE HTTP server closed"));
10477
10566
  await Promise.allSettled(Object.values(transports).map(async (transport) => {
10478
10567
  try {