@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/README.ko.md +20 -5
- package/README.md +19 -6
- package/README.zh-CN.md +19 -6
- package/build/downloads/proxy.js +14 -7
- package/build/index.js +329 -240
- package/build/scripts/generate-tool-docs.js +3 -1
- package/build/test/path-segment-encoding.test.js +73 -17
- package/build/test/sse-session-limits.test.js +273 -0
- package/build/test/test-download-attachment.js +11 -0
- package/build/test/test-geteffectiveprojectid.js +1 -1
- package/build/test/test-job-artifacts.js +22 -0
- package/build/test/test-permission-mode.js +103 -12
- package/build/test/test-remote-downloads.js +14 -1
- package/build/test/utils/graphql-query.test.js +72 -0
- package/build/test/utils/safe-redirect-fetch.test.js +463 -0
- package/build/test/utils/tool-args.test.js +32 -0
- package/build/test/utils/url.test.js +128 -0
- package/build/test-note.js +2 -1
- package/build/test-resolve-issue-note.js +4 -3
- package/build/tools/registry.js +16 -2
- package/build/utils/graphql-query.js +31 -9
- package/build/utils/safe-redirect-fetch.js +321 -0
- package/build/utils/tool-args.js +12 -3
- package/build/utils/url.js +102 -0
- package/package.json +1 -1
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/${
|
|
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/${
|
|
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/${
|
|
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 =
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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 =
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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 =
|
|
3535
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
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/${
|
|
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 =
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
5560
|
-
const response = await
|
|
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
|
-
|
|
5588
|
-
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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 =
|
|
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 =
|
|
6781
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${
|
|
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 =
|
|
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 =
|
|
6805
|
+
const encoded = encodeGitLabPathSegment(groupId);
|
|
6803
6806
|
const { filter, ...body } = options;
|
|
6804
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${
|
|
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 =
|
|
6819
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${
|
|
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 =
|
|
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/${
|
|
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/${
|
|
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/${
|
|
7288
|
-
const response = await
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10501
|
+
const sessionId = transport.sessionId;
|
|
10502
|
+
transports[sessionId] = transport;
|
|
10503
|
+
sessionLastActivity.set(sessionId, Date.now());
|
|
10444
10504
|
res.on("close", () => {
|
|
10445
|
-
delete transports[
|
|
10505
|
+
delete transports[sessionId];
|
|
10506
|
+
sessionLastActivity.delete(sessionId);
|
|
10446
10507
|
});
|
|
10447
|
-
|
|
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
|
-
|
|
10462
|
-
|
|
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 {
|