@zereight/mcp-gitlab 2.1.64 → 2.1.66
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 +21 -5
- package/README.md +20 -6
- package/README.zh-CN.md +20 -6
- package/build/downloads/proxy.js +14 -7
- package/build/index.js +364 -244
- package/build/scripts/generate-tool-docs.js +3 -1
- package/build/test/path-segment-encoding.test.js +73 -17
- package/build/test/response-masking.test.js +33 -0
- 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/jmespath-tool-result.test.js +91 -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/jmespath-tool-result.js +78 -0
- 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 +3 -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,9 +98,11 @@ 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";
|
|
105
|
+
import { applyJmespathToToolResult, getJmespathSyntaxError, JMESPATH_TOOL_ARGUMENT, JMESPATH_TOOL_ARGUMENT_DESCRIPTION, omitJmespathArgument, readJmespathExpression, } from "./utils/jmespath-tool-result.js";
|
|
104
106
|
import { parseSearchReplaceBlocks, applySearchReplace, applyUnifiedDiff, } from "./utils/patch-helper.js";
|
|
105
107
|
import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
|
|
106
108
|
import { GitLabClientPool } from "./gitlab-client-pool.js";
|
|
@@ -288,6 +290,18 @@ function createServer() {
|
|
|
288
290
|
},
|
|
289
291
|
};
|
|
290
292
|
}
|
|
293
|
+
if (modified.inputSchema && typeof modified.inputSchema === "object") {
|
|
294
|
+
const properties = modified.inputSchema.properties && typeof modified.inputSchema.properties === "object"
|
|
295
|
+
? { ...modified.inputSchema.properties }
|
|
296
|
+
: {};
|
|
297
|
+
if (!(JMESPATH_TOOL_ARGUMENT in properties)) {
|
|
298
|
+
properties[JMESPATH_TOOL_ARGUMENT] = {
|
|
299
|
+
type: "string",
|
|
300
|
+
description: JMESPATH_TOOL_ARGUMENT_DESCRIPTION,
|
|
301
|
+
};
|
|
302
|
+
modified.inputSchema = { ...modified.inputSchema, properties };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
291
305
|
return modified;
|
|
292
306
|
});
|
|
293
307
|
// <<< END: Remove $schema for Gemini compatibility >>>
|
|
@@ -302,15 +316,24 @@ function createServer() {
|
|
|
302
316
|
const toolName = request.params.name;
|
|
303
317
|
const start = Date.now();
|
|
304
318
|
let maskingEngine;
|
|
305
|
-
const
|
|
319
|
+
const jmespathExpression = readJmespathExpression(request.params.arguments);
|
|
320
|
+
const logCompletion = (result, completionOptions) => {
|
|
306
321
|
const durationMs = Date.now() - start;
|
|
307
322
|
logger.info({ tool: toolName, event: "tool_call_done", durationMs }, `tool_call_done: ${toolName} (${durationMs}ms)`);
|
|
308
323
|
const isPlainTextResult = toolName === "get_pipeline_job_output" ||
|
|
309
324
|
toolName === "get_job_artifact_file" ||
|
|
310
325
|
(toolName === "download_release_asset" && !IS_REMOTE);
|
|
326
|
+
const filtered = applyJmespathToToolResult(result, jmespathExpression, {
|
|
327
|
+
...completionOptions,
|
|
328
|
+
maskValue: maskingEngine
|
|
329
|
+
? (value) => maskingEngine.maskValue(value)
|
|
330
|
+
: undefined,
|
|
331
|
+
});
|
|
311
332
|
return maskingEngine
|
|
312
|
-
? maskingEngine.maskToolResult(
|
|
313
|
-
|
|
333
|
+
? maskingEngine.maskToolResult(filtered, {
|
|
334
|
+
textFormat: isPlainTextResult ? "plain" : "json",
|
|
335
|
+
})
|
|
336
|
+
: filtered;
|
|
314
337
|
};
|
|
315
338
|
const logError = (error) => {
|
|
316
339
|
const durationMs = Date.now() - start;
|
|
@@ -369,6 +392,12 @@ function createServer() {
|
|
|
369
392
|
maskingEngine = sessionContext
|
|
370
393
|
? sessionAuthStore.run(sessionContext, selectMaskingPolicy)
|
|
371
394
|
: selectMaskingPolicy();
|
|
395
|
+
const jmespathSyntaxError = jmespathExpression
|
|
396
|
+
? getJmespathSyntaxError(jmespathExpression)
|
|
397
|
+
: undefined;
|
|
398
|
+
if (jmespathSyntaxError) {
|
|
399
|
+
return logCompletion({ content: [{ type: "text", text: jmespathSyntaxError }], isError: true }, { skipJmespath: true });
|
|
400
|
+
}
|
|
372
401
|
// Handle discover_tools meta-tool directly (needs access to mcpServer and filteredTools)
|
|
373
402
|
if (toolName === "discover_tools") {
|
|
374
403
|
const category = request.params.arguments?.category?.trim()?.toLowerCase();
|
|
@@ -476,12 +505,15 @@ function createServer() {
|
|
|
476
505
|
text: `Tool "${toolName}" requires confirmation. This tool is marked as requiring approval before execution. Re-call with _confirmed: true to proceed.`,
|
|
477
506
|
},
|
|
478
507
|
],
|
|
479
|
-
});
|
|
508
|
+
}, { skipJmespath: true });
|
|
480
509
|
}
|
|
481
510
|
// Strip _confirmed from args before forwarding to handler
|
|
482
511
|
const { _confirmed, ...cleanArgs } = request.params.arguments || {};
|
|
483
512
|
request.params.arguments = cleanArgs;
|
|
484
513
|
}
|
|
514
|
+
if (request.params.arguments && typeof request.params.arguments === "object") {
|
|
515
|
+
request.params.arguments = omitJmespathArgument(request.params.arguments);
|
|
516
|
+
}
|
|
485
517
|
if (sessionContext) {
|
|
486
518
|
// Policy selection and the tool handler must use the same request-local
|
|
487
519
|
// API URL and project allowlist.
|
|
@@ -829,7 +861,7 @@ async function ensureValidOAuthToken() {
|
|
|
829
861
|
throw error;
|
|
830
862
|
}
|
|
831
863
|
}
|
|
832
|
-
// Permission mode gate: readonly exposes only read tools; modify blocks delete tools; full allows all
|
|
864
|
+
// Permission mode gate: readonly exposes only read tools; modify blocks delete/teardown tools; full allows all
|
|
833
865
|
function isToolAllowedByPermissionMode(toolName) {
|
|
834
866
|
switch (GITLAB_PERMISSION_MODE) {
|
|
835
867
|
case "readonly":
|
|
@@ -1165,6 +1197,21 @@ const getFetchConfig = () => {
|
|
|
1165
1197
|
dispatcher,
|
|
1166
1198
|
};
|
|
1167
1199
|
};
|
|
1200
|
+
/**
|
|
1201
|
+
* Options for the download sinks that stream a file through `fetchWithValidatedRedirects`.
|
|
1202
|
+
*
|
|
1203
|
+
* GitLab answers a release asset, a job artifact or an uploaded file with a redirect to
|
|
1204
|
+
* its object storage, so those hops leave the origin and must be validated. The client
|
|
1205
|
+
* split matters as much as the headers: the wrapped client adds the cookie jar and a
|
|
1206
|
+
* fresh `Authorization` on a `401`, so a hop whose credentials were withheld has to use
|
|
1207
|
+
* plain undici instead.
|
|
1208
|
+
*/
|
|
1209
|
+
const downloadRedirectOptions = () => ({
|
|
1210
|
+
...getFetchConfig(),
|
|
1211
|
+
fetchImpl: fetch,
|
|
1212
|
+
unauthenticatedFetchImpl: undiciFetch,
|
|
1213
|
+
isTrustedRedirectHost: isTrustedGitLabRedirectHost,
|
|
1214
|
+
});
|
|
1168
1215
|
// Compute at startup
|
|
1169
1216
|
const enabledToolsets = parseEnabledToolsets(GITLAB_TOOLSETS_RAW);
|
|
1170
1217
|
const individuallyEnabledTools = parseIndividualTools(GITLAB_TOOLS_RAW);
|
|
@@ -1195,23 +1242,6 @@ function parseAllowedGitLabApiUrls(value) {
|
|
|
1195
1242
|
.map(toAllowedGitLabApiUrl)
|
|
1196
1243
|
.filter((entry) => Boolean(entry));
|
|
1197
1244
|
}
|
|
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
1245
|
function resolveTrustedGitLabApiUrl(value) {
|
|
1216
1246
|
const parsed = new URL(normalizeGitLabApiUrl(value));
|
|
1217
1247
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
@@ -1238,6 +1268,17 @@ for (const { host, apiUrl } of [
|
|
|
1238
1268
|
GITLAB_ALLOWED_API_URLS_BY_HOST.set(host, apiUrl);
|
|
1239
1269
|
}
|
|
1240
1270
|
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Redirects from the GitLab API to operator-declared GitLab hosts are followed even
|
|
1273
|
+
* when the host resolves to a private address (self-hosted instances and their
|
|
1274
|
+
* storage may live on an internal network), and those hosts keep receiving the
|
|
1275
|
+
* request credentials. Every other redirect target must resolve to a public
|
|
1276
|
+
* address and is fetched without credentials.
|
|
1277
|
+
*
|
|
1278
|
+
* The lookup key is `URL.host`, which keeps a non-default port, matching how this
|
|
1279
|
+
* map is built from GITLAB_API_URL / GITLAB_ALLOWED_HOSTS.
|
|
1280
|
+
*/
|
|
1281
|
+
const isTrustedGitLabRedirectHost = (host) => GITLAB_ALLOWED_API_URLS_BY_HOST.has(host);
|
|
1241
1282
|
const GITLAB_PROJECT_ID = process.env.GITLAB_PROJECT_ID;
|
|
1242
1283
|
const GITLAB_ALLOWED_PROJECT_IDS = process.env.GITLAB_ALLOWED_PROJECT_IDS?.split(",")
|
|
1243
1284
|
.map(id => id.trim())
|
|
@@ -1364,7 +1405,7 @@ function rejectIfProjectScopedDeployment(toolName) {
|
|
|
1364
1405
|
async function forkProject(projectId, namespace) {
|
|
1365
1406
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1366
1407
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
1367
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
1408
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/fork`);
|
|
1368
1409
|
if (namespace) {
|
|
1369
1410
|
url.searchParams.append("namespace", namespace);
|
|
1370
1411
|
}
|
|
@@ -1391,7 +1432,7 @@ async function forkProject(projectId, namespace) {
|
|
|
1391
1432
|
async function createBranch(projectId, options) {
|
|
1392
1433
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1393
1434
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
1394
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
1435
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/repository/branches`);
|
|
1395
1436
|
const response = await fetch(url.toString(), {
|
|
1396
1437
|
...getFetchConfig(),
|
|
1397
1438
|
method: "POST",
|
|
@@ -1413,7 +1454,7 @@ async function createBranch(projectId, options) {
|
|
|
1413
1454
|
async function getDefaultBranchRef(projectId) {
|
|
1414
1455
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1415
1456
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
1416
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
1457
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}`);
|
|
1417
1458
|
const response = await fetch(url.toString(), {
|
|
1418
1459
|
...getFetchConfig(),
|
|
1419
1460
|
});
|
|
@@ -1433,12 +1474,12 @@ async function getDefaultBranchRef(projectId) {
|
|
|
1433
1474
|
async function getFileContents(projectId, filePath, ref) {
|
|
1434
1475
|
const decodedProjectId = projectId ? decodeURIComponent(projectId) : "";
|
|
1435
1476
|
const effectiveProjectId = getEffectiveProjectId(decodedProjectId);
|
|
1436
|
-
const encodedPath =
|
|
1477
|
+
const encodedPath = encodeGitLabPathSegment(filePath);
|
|
1437
1478
|
// Fall back to default branch if ref is not provided
|
|
1438
1479
|
if (!ref) {
|
|
1439
1480
|
ref = await getDefaultBranchRef(decodedProjectId);
|
|
1440
1481
|
}
|
|
1441
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
1482
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/repository/files/${encodedPath}`);
|
|
1442
1483
|
url.searchParams.append("ref", ref);
|
|
1443
1484
|
const response = await fetch(url.toString(), {
|
|
1444
1485
|
...getFetchConfig(),
|
|
@@ -1464,7 +1505,7 @@ async function getFileContents(projectId, filePath, ref) {
|
|
|
1464
1505
|
async function createIssue(projectId, options) {
|
|
1465
1506
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1466
1507
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
1467
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
1508
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/issues`);
|
|
1468
1509
|
// Build request body, converting labels array to comma-separated string
|
|
1469
1510
|
const body = { ...options };
|
|
1470
1511
|
if (body.labels && Array.isArray(body.labels)) {
|
|
@@ -1499,7 +1540,7 @@ async function listIssues(projectId, options = {}) {
|
|
|
1499
1540
|
// resolve the default project (or fail for a multi-project allowlist)
|
|
1500
1541
|
const decodedProjectId = projectId ? decodeURIComponent(projectId) : "";
|
|
1501
1542
|
const effectiveProjectId = getEffectiveProjectId(decodedProjectId);
|
|
1502
|
-
url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
1543
|
+
url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/issues`);
|
|
1503
1544
|
}
|
|
1504
1545
|
else {
|
|
1505
1546
|
url = new URL(`${getEffectiveApiUrl()}/issues`);
|
|
@@ -1600,7 +1641,7 @@ async function listMergeRequests(projectId, options = {}) {
|
|
|
1600
1641
|
// In strict scope, never fall back to the instance-wide endpoint;
|
|
1601
1642
|
// resolve the default project (or fail for a multi-project allowlist)
|
|
1602
1643
|
const effectiveProjectId = getEffectiveProjectId(decodedProjectId);
|
|
1603
|
-
url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
1644
|
+
url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/merge_requests`);
|
|
1604
1645
|
}
|
|
1605
1646
|
else {
|
|
1606
1647
|
url = new URL(`${getEffectiveApiUrl()}/merge_requests`);
|
|
@@ -1622,7 +1663,7 @@ async function listMergeRequests(projectId, options = {}) {
|
|
|
1622
1663
|
*/
|
|
1623
1664
|
async function listGroupMergeRequests(groupId, options = {}) {
|
|
1624
1665
|
const decodedGroupId = decodeURIComponent(groupId);
|
|
1625
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${
|
|
1666
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(decodedGroupId)}/merge_requests`);
|
|
1626
1667
|
appendMergeRequestFilters(url, options);
|
|
1627
1668
|
const response = await fetch(url.toString(), {
|
|
1628
1669
|
...getFetchConfig(),
|
|
@@ -1641,7 +1682,7 @@ async function listGroupMergeRequests(groupId, options = {}) {
|
|
|
1641
1682
|
*/
|
|
1642
1683
|
async function getIssue(projectId, issueIid) {
|
|
1643
1684
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1644
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
1685
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}`);
|
|
1645
1686
|
const response = await fetch(url.toString(), {
|
|
1646
1687
|
...getFetchConfig(),
|
|
1647
1688
|
});
|
|
@@ -1660,7 +1701,7 @@ async function getIssue(projectId, issueIid) {
|
|
|
1660
1701
|
*/
|
|
1661
1702
|
async function updateIssue(projectId, issueIid, options) {
|
|
1662
1703
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1663
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
1704
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}`);
|
|
1664
1705
|
// Convert labels array to comma-separated string if present
|
|
1665
1706
|
const body = { ...options };
|
|
1666
1707
|
if (body.labels && Array.isArray(body.labels)) {
|
|
@@ -1685,7 +1726,7 @@ async function updateIssue(projectId, issueIid, options) {
|
|
|
1685
1726
|
*/
|
|
1686
1727
|
async function deleteIssue(projectId, issueIid) {
|
|
1687
1728
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1688
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
1729
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}`);
|
|
1689
1730
|
const response = await fetch(url.toString(), {
|
|
1690
1731
|
...getFetchConfig(),
|
|
1691
1732
|
method: "DELETE",
|
|
@@ -2298,7 +2339,7 @@ async function resolveProjectOrGroupPath(projectId) {
|
|
|
2298
2339
|
}
|
|
2299
2340
|
const effectiveProjectId = getEffectiveProjectId(requestedProjectId);
|
|
2300
2341
|
if (explicitKind === "group") {
|
|
2301
|
-
const groupUrl = new URL(`${getEffectiveApiUrl()}/groups/${
|
|
2342
|
+
const groupUrl = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(effectiveProjectId)}`);
|
|
2302
2343
|
const groupResponse = await fetch(groupUrl.toString(), {
|
|
2303
2344
|
...getFetchConfig(),
|
|
2304
2345
|
});
|
|
@@ -2306,7 +2347,7 @@ async function resolveProjectOrGroupPath(projectId) {
|
|
|
2306
2347
|
const group = await groupResponse.json();
|
|
2307
2348
|
return { path: group.full_path, kind: "group" };
|
|
2308
2349
|
}
|
|
2309
|
-
const projectUrl = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
2350
|
+
const projectUrl = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}`);
|
|
2310
2351
|
const projectResponse = await fetch(projectUrl.toString(), {
|
|
2311
2352
|
...getFetchConfig(),
|
|
2312
2353
|
});
|
|
@@ -2318,7 +2359,7 @@ async function resolveProjectOrGroupPath(projectId) {
|
|
|
2318
2359
|
!explicitKind &&
|
|
2319
2360
|
getEffectiveAllowedProjectIds().length === 0 &&
|
|
2320
2361
|
!/^\d+$/.test(effectiveProjectId)) {
|
|
2321
|
-
const groupUrl = new URL(`${getEffectiveApiUrl()}/groups/${
|
|
2362
|
+
const groupUrl = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(effectiveProjectId)}`);
|
|
2322
2363
|
const groupResponse = await fetch(groupUrl.toString(), {
|
|
2323
2364
|
...getFetchConfig(),
|
|
2324
2365
|
});
|
|
@@ -3059,7 +3100,7 @@ async function updateWorkItem(projectId, iid, options) {
|
|
|
3059
3100
|
*/
|
|
3060
3101
|
async function listIssueLinks(projectId, issueIid) {
|
|
3061
3102
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3062
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3103
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}/links`);
|
|
3063
3104
|
const response = await fetch(url.toString(), {
|
|
3064
3105
|
...getFetchConfig(),
|
|
3065
3106
|
});
|
|
@@ -3078,7 +3119,7 @@ async function listIssueLinks(projectId, issueIid) {
|
|
|
3078
3119
|
*/
|
|
3079
3120
|
async function getIssueLink(projectId, issueIid, issueLinkId) {
|
|
3080
3121
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3081
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3122
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}/links/${encodeGitLabPathSegment(issueLinkId)}`);
|
|
3082
3123
|
const response = await fetch(url.toString(), {
|
|
3083
3124
|
...getFetchConfig(),
|
|
3084
3125
|
});
|
|
@@ -3100,7 +3141,7 @@ async function getIssueLink(projectId, issueIid, issueLinkId) {
|
|
|
3100
3141
|
async function createIssueLink(projectId, issueIid, targetProjectId, targetIssueIid, linkType = "relates_to") {
|
|
3101
3142
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3102
3143
|
targetProjectId = decodeURIComponent(targetProjectId); // Decode target project ID as well
|
|
3103
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3144
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}/links`);
|
|
3104
3145
|
const response = await fetch(url.toString(), {
|
|
3105
3146
|
...getFetchConfig(),
|
|
3106
3147
|
method: "POST",
|
|
@@ -3125,7 +3166,7 @@ async function createIssueLink(projectId, issueIid, targetProjectId, targetIssue
|
|
|
3125
3166
|
*/
|
|
3126
3167
|
async function deleteIssueLink(projectId, issueIid, issueLinkId) {
|
|
3127
3168
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3128
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3169
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}/links/${encodeGitLabPathSegment(issueLinkId)}`);
|
|
3129
3170
|
const response = await fetch(url.toString(), {
|
|
3130
3171
|
...getFetchConfig(),
|
|
3131
3172
|
method: "DELETE",
|
|
@@ -3142,7 +3183,7 @@ async function deleteIssueLink(projectId, issueIid, issueLinkId) {
|
|
|
3142
3183
|
*/
|
|
3143
3184
|
async function createMergeRequest(projectId, options) {
|
|
3144
3185
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3145
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3186
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests`);
|
|
3146
3187
|
const response = await fetch(url.toString(), {
|
|
3147
3188
|
...getFetchConfig(),
|
|
3148
3189
|
method: "POST",
|
|
@@ -3184,7 +3225,7 @@ async function createMergeRequest(projectId, options) {
|
|
|
3184
3225
|
*/
|
|
3185
3226
|
async function listDiscussions(projectId, resourceType, resourceIid, options = {}) {
|
|
3186
3227
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3187
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3228
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/${resourceType}/${encodeGitLabPathSegment(resourceIid)}/discussions`);
|
|
3188
3229
|
// Add query parameters for pagination and sorting
|
|
3189
3230
|
if (options.page) {
|
|
3190
3231
|
url.searchParams.append("page", options.page.toString());
|
|
@@ -3248,7 +3289,7 @@ async function listIssueDiscussions(projectId, issueIid, options = {}) {
|
|
|
3248
3289
|
}
|
|
3249
3290
|
async function deleteMergeRequestDiscussionNote(projectId, mergeRequestIid, discussionId, noteId) {
|
|
3250
3291
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3251
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3292
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}/notes/${encodeGitLabPathSegment(noteId)}`);
|
|
3252
3293
|
const response = await fetch(url.toString(), {
|
|
3253
3294
|
...getFetchConfig(),
|
|
3254
3295
|
method: "DELETE",
|
|
@@ -3272,7 +3313,7 @@ async function deleteMergeRequestDiscussionNote(projectId, mergeRequestIid, disc
|
|
|
3272
3313
|
*/
|
|
3273
3314
|
async function updateMergeRequestDiscussionNote(projectId, mergeRequestIid, discussionId, noteId, body, resolved) {
|
|
3274
3315
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3275
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3316
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}/notes/${encodeGitLabPathSegment(noteId)}`);
|
|
3276
3317
|
// Only one of body or resolved can be sent according to GitLab API
|
|
3277
3318
|
const payload = {};
|
|
3278
3319
|
if (body !== undefined) {
|
|
@@ -3318,7 +3359,7 @@ async function updateMergeRequestDiscussionNote(projectId, mergeRequestIid, disc
|
|
|
3318
3359
|
*/
|
|
3319
3360
|
async function updateIssueNote(projectId, issueIid, discussionId, noteId, body, resolved) {
|
|
3320
3361
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3321
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3362
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}/discussions/${encodeGitLabPathSegment(discussionId)}/notes/${encodeGitLabPathSegment(noteId)}`);
|
|
3322
3363
|
// Only one of body or resolved can be sent according to GitLab API
|
|
3323
3364
|
const payload = {};
|
|
3324
3365
|
if (body !== undefined) {
|
|
@@ -3347,7 +3388,7 @@ async function updateIssueNote(projectId, issueIid, discussionId, noteId, body,
|
|
|
3347
3388
|
*/
|
|
3348
3389
|
async function createIssueNote(projectId, issueIid, discussionId, body, createdAt) {
|
|
3349
3390
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3350
|
-
const basePath = `${getEffectiveApiUrl()}/projects/${
|
|
3391
|
+
const basePath = `${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/issues/${encodeGitLabPathSegment(issueIid)}`;
|
|
3351
3392
|
const url = new URL(discussionId ? `${basePath}/discussions/${encodeGitLabPathSegment(discussionId)}/notes` : `${basePath}/notes`);
|
|
3352
3393
|
const payload = { body };
|
|
3353
3394
|
if (createdAt) {
|
|
@@ -3375,7 +3416,7 @@ async function createIssueNote(projectId, issueIid, discussionId, body, createdA
|
|
|
3375
3416
|
*/
|
|
3376
3417
|
async function createMergeRequestDiscussionNote(projectId, mergeRequestIid, discussionId, body, createdAt) {
|
|
3377
3418
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3378
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3419
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}/notes`);
|
|
3379
3420
|
const payload = { body };
|
|
3380
3421
|
if (createdAt) {
|
|
3381
3422
|
payload.created_at = createdAt;
|
|
@@ -3391,7 +3432,7 @@ async function createMergeRequestDiscussionNote(projectId, mergeRequestIid, disc
|
|
|
3391
3432
|
}
|
|
3392
3433
|
async function createMergeRequestNote(projectId, mergeRequestIid, body) {
|
|
3393
3434
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3394
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3435
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes`);
|
|
3395
3436
|
const payload = {
|
|
3396
3437
|
id: projectId,
|
|
3397
3438
|
merge_request_iid: mergeRequestIid,
|
|
@@ -3408,7 +3449,7 @@ async function createMergeRequestNote(projectId, mergeRequestIid, body) {
|
|
|
3408
3449
|
}
|
|
3409
3450
|
async function deleteMergeRequestNote(projectId, mergeRequestIid, noteId) {
|
|
3410
3451
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3411
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3452
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes/${encodeGitLabPathSegment(noteId)}`);
|
|
3412
3453
|
const response = await fetch(url.toString(), {
|
|
3413
3454
|
...getFetchConfig(),
|
|
3414
3455
|
method: "DELETE",
|
|
@@ -3421,7 +3462,7 @@ async function deleteMergeRequestNote(projectId, mergeRequestIid, noteId) {
|
|
|
3421
3462
|
// --- Emoji Reactions (REST) ---
|
|
3422
3463
|
function buildAwardEmojiPath(entity, projectId, entityIid, opts) {
|
|
3423
3464
|
projectId = decodeURIComponent(projectId);
|
|
3424
|
-
const pp =
|
|
3465
|
+
const pp = encodeGitLabPathSegment(getEffectiveProjectId(projectId));
|
|
3425
3466
|
let path = `${getEffectiveApiUrl()}/projects/${pp}/${entity}/${encodeGitLabPathSegment(entityIid)}`;
|
|
3426
3467
|
if (opts?.noteId) {
|
|
3427
3468
|
path = opts.discussionId
|
|
@@ -3456,7 +3497,7 @@ async function deleteRestAwardEmoji(path) {
|
|
|
3456
3497
|
}
|
|
3457
3498
|
async function getMergeRequestNote(projectId, mergeRequestIid, noteId) {
|
|
3458
3499
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3459
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3500
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes/${encodeGitLabPathSegment(noteId)}`);
|
|
3460
3501
|
const response = await fetch(url.toString(), {
|
|
3461
3502
|
...getFetchConfig(),
|
|
3462
3503
|
method: "GET",
|
|
@@ -3467,7 +3508,7 @@ async function getMergeRequestNote(projectId, mergeRequestIid, noteId) {
|
|
|
3467
3508
|
}
|
|
3468
3509
|
async function getMergeRequestDiscussion(projectId, mergeRequestIid, discussionId) {
|
|
3469
3510
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3470
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3511
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}`);
|
|
3471
3512
|
const response = await fetch(url.toString(), {
|
|
3472
3513
|
...getFetchConfig(),
|
|
3473
3514
|
method: "GET",
|
|
@@ -3478,7 +3519,7 @@ async function getMergeRequestDiscussion(projectId, mergeRequestIid, discussionI
|
|
|
3478
3519
|
}
|
|
3479
3520
|
async function getMergeRequestNotes(projectId, mergeRequestIid, sort, order_by, per_page, page) {
|
|
3480
3521
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3481
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3522
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes`);
|
|
3482
3523
|
if (sort) {
|
|
3483
3524
|
url.searchParams.append("sort", sort);
|
|
3484
3525
|
}
|
|
@@ -3501,7 +3542,7 @@ async function getMergeRequestNotes(projectId, mergeRequestIid, sort, order_by,
|
|
|
3501
3542
|
}
|
|
3502
3543
|
async function updateMergeRequestNote(projectId, mergeRequestIid, noteId, body) {
|
|
3503
3544
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3504
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3545
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes/${encodeGitLabPathSegment(noteId)}`);
|
|
3505
3546
|
const payload = {
|
|
3506
3547
|
id: projectId,
|
|
3507
3548
|
merge_request_iid: mergeRequestIid,
|
|
@@ -3531,8 +3572,8 @@ async function updateMergeRequestNote(projectId, mergeRequestIid, noteId, body)
|
|
|
3531
3572
|
*/
|
|
3532
3573
|
async function createOrUpdateFile(projectId, filePath, content, commitMessage, branch, previousPath, last_commit_id, commit_id, encoding) {
|
|
3533
3574
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3534
|
-
const encodedPath =
|
|
3535
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3575
|
+
const encodedPath = encodeGitLabPathSegment(filePath);
|
|
3576
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/files/${encodedPath}`);
|
|
3536
3577
|
const resolvedEncoding = encoding ?? GITLAB_REPO_FILE_ENCODING;
|
|
3537
3578
|
const body = {
|
|
3538
3579
|
branch,
|
|
@@ -3603,7 +3644,7 @@ async function createOrUpdateFile(projectId, filePath, content, commitMessage, b
|
|
|
3603
3644
|
*/
|
|
3604
3645
|
async function createCommit(projectId, message, branch, actions) {
|
|
3605
3646
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3606
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3647
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/commits`);
|
|
3607
3648
|
const response = await fetch(url.toString(), {
|
|
3608
3649
|
...getFetchConfig(),
|
|
3609
3650
|
method: "POST",
|
|
@@ -3672,7 +3713,7 @@ async function searchBlobs(params) {
|
|
|
3672
3713
|
let basePath;
|
|
3673
3714
|
if (params.project_id) {
|
|
3674
3715
|
const decodedProjectId = decodeURIComponent(params.project_id);
|
|
3675
|
-
const projectId =
|
|
3716
|
+
const projectId = encodeGitLabPathSegment(getEffectiveProjectId(decodedProjectId));
|
|
3676
3717
|
basePath = `${getEffectiveApiUrl()}/projects/${projectId}/search`;
|
|
3677
3718
|
}
|
|
3678
3719
|
else if (params.group_id) {
|
|
@@ -3750,11 +3791,11 @@ async function getMergeRequest(projectId, mergeRequestIid, branchName) {
|
|
|
3750
3791
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3751
3792
|
let url;
|
|
3752
3793
|
if (mergeRequestIid) {
|
|
3753
|
-
url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3794
|
+
url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}`);
|
|
3754
3795
|
url.searchParams.append("include_diverged_commits_count", "true");
|
|
3755
3796
|
}
|
|
3756
3797
|
else if (branchName) {
|
|
3757
|
-
url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3798
|
+
url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests?source_branch=${encodeURIComponent(branchName)}`);
|
|
3758
3799
|
}
|
|
3759
3800
|
else {
|
|
3760
3801
|
throw new Error("Either mergeRequestIid or branchName must be provided");
|
|
@@ -3772,7 +3813,7 @@ async function getMergeRequest(projectId, mergeRequestIid, branchName) {
|
|
|
3772
3813
|
return GitLabMergeRequestSchema.parse(data);
|
|
3773
3814
|
}
|
|
3774
3815
|
async function getMergeRequestSourceCommitCount(projectId, mergeRequestIid) {
|
|
3775
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3816
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/commits`);
|
|
3776
3817
|
url.searchParams.append("per_page", "100");
|
|
3777
3818
|
let totalCount = 0;
|
|
3778
3819
|
let page = 1;
|
|
@@ -3800,7 +3841,7 @@ async function getMergeRequestSourceCommitCount(projectId, mergeRequestIid) {
|
|
|
3800
3841
|
}
|
|
3801
3842
|
async function listMergeRequestPipelines(projectId, mergeRequestIid, options = {}) {
|
|
3802
3843
|
projectId = decodeURIComponent(projectId);
|
|
3803
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3844
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/pipelines`);
|
|
3804
3845
|
appendFilterParams(url.searchParams, options);
|
|
3805
3846
|
const response = await fetch(url.toString(), {
|
|
3806
3847
|
...getFetchConfig(),
|
|
@@ -3810,7 +3851,7 @@ async function listMergeRequestPipelines(projectId, mergeRequestIid, options = {
|
|
|
3810
3851
|
return z.array(GitLabMergeRequestPipelineSchema).parse(data);
|
|
3811
3852
|
}
|
|
3812
3853
|
async function getProjectMergeMethod(projectId) {
|
|
3813
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
3854
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}`);
|
|
3814
3855
|
const response = await fetch(url.toString(), {
|
|
3815
3856
|
...getFetchConfig(),
|
|
3816
3857
|
});
|
|
@@ -3997,7 +4038,7 @@ async function getMergeRequestDiffs(projectId, mergeRequestIid, branchName, view
|
|
|
3997
4038
|
const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
|
|
3998
4039
|
mergeRequestIid = mergeRequest.iid;
|
|
3999
4040
|
}
|
|
4000
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4041
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/changes`);
|
|
4001
4042
|
if (view) {
|
|
4002
4043
|
url.searchParams.append("view", view);
|
|
4003
4044
|
}
|
|
@@ -4027,7 +4068,7 @@ async function listMergeRequestDiffs(projectId, mergeRequestIid, branchName, pag
|
|
|
4027
4068
|
const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
|
|
4028
4069
|
mergeRequestIid = mergeRequest.iid;
|
|
4029
4070
|
}
|
|
4030
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4071
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/diffs`);
|
|
4031
4072
|
if (page) {
|
|
4032
4073
|
url.searchParams.append("page", page.toString());
|
|
4033
4074
|
}
|
|
@@ -4063,7 +4104,7 @@ async function listMergeRequestChangedFiles(projectId, mergeRequestIid, branchNa
|
|
|
4063
4104
|
const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
|
|
4064
4105
|
mergeRequestIid = mergeRequest.iid;
|
|
4065
4106
|
}
|
|
4066
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4107
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/changes`);
|
|
4067
4108
|
const response = await fetch(url.toString(), { ...getFetchConfig() });
|
|
4068
4109
|
await handleGitLabError(response);
|
|
4069
4110
|
const data = (await response.json());
|
|
@@ -4104,7 +4145,7 @@ async function getMergeRequestFileDiff(projectId, filePaths, mergeRequestIid, br
|
|
|
4104
4145
|
let page = 1;
|
|
4105
4146
|
const perPage = 20;
|
|
4106
4147
|
while (remaining.size > 0) {
|
|
4107
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4148
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/diffs`);
|
|
4108
4149
|
url.searchParams.append("page", page.toString());
|
|
4109
4150
|
url.searchParams.append("per_page", perPage.toString());
|
|
4110
4151
|
if (unidiff) {
|
|
@@ -4147,7 +4188,7 @@ async function getMergeRequestFileDiff(projectId, filePaths, mergeRequestIid, br
|
|
|
4147
4188
|
*/
|
|
4148
4189
|
async function getBranchDiffs(projectId, from, to, straight) {
|
|
4149
4190
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
4150
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4191
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/compare`);
|
|
4151
4192
|
url.searchParams.append("from", from);
|
|
4152
4193
|
url.searchParams.append("to", to);
|
|
4153
4194
|
if (straight !== undefined) {
|
|
@@ -4182,7 +4223,7 @@ async function updateMergeRequest(projectId, options, mergeRequestIid, branchNam
|
|
|
4182
4223
|
const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
|
|
4183
4224
|
mergeRequestIid = mergeRequest.iid;
|
|
4184
4225
|
}
|
|
4185
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4226
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}`);
|
|
4186
4227
|
const response = await fetch(url.toString(), {
|
|
4187
4228
|
...getFetchConfig(),
|
|
4188
4229
|
method: "PUT",
|
|
@@ -4202,7 +4243,7 @@ async function updateMergeRequest(projectId, options, mergeRequestIid, branchNam
|
|
|
4202
4243
|
*/
|
|
4203
4244
|
async function mergeMergeRequest(projectId, options, mergeRequestIid) {
|
|
4204
4245
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
4205
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4246
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/merge`);
|
|
4206
4247
|
const response = await fetch(url.toString(), {
|
|
4207
4248
|
...getFetchConfig(),
|
|
4208
4249
|
method: "PUT",
|
|
@@ -4222,7 +4263,7 @@ async function mergeMergeRequest(projectId, options, mergeRequestIid) {
|
|
|
4222
4263
|
*/
|
|
4223
4264
|
async function approveMergeRequest(projectId, mergeRequestIid, sha, approvalPassword) {
|
|
4224
4265
|
projectId = decodeURIComponent(projectId);
|
|
4225
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4266
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/approve`);
|
|
4226
4267
|
const body = {};
|
|
4227
4268
|
if (sha) {
|
|
4228
4269
|
body.sha = sha;
|
|
@@ -4247,7 +4288,7 @@ async function approveMergeRequest(projectId, mergeRequestIid, sha, approvalPass
|
|
|
4247
4288
|
*/
|
|
4248
4289
|
async function unapproveMergeRequest(projectId, mergeRequestIid) {
|
|
4249
4290
|
projectId = decodeURIComponent(projectId);
|
|
4250
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4291
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/unapprove`);
|
|
4251
4292
|
const response = await fetch(url.toString(), {
|
|
4252
4293
|
...getFetchConfig(),
|
|
4253
4294
|
method: "POST",
|
|
@@ -4265,7 +4306,7 @@ async function unapproveMergeRequest(projectId, mergeRequestIid) {
|
|
|
4265
4306
|
*/
|
|
4266
4307
|
async function getMergeRequestApprovalState(projectId, mergeRequestIid) {
|
|
4267
4308
|
projectId = decodeURIComponent(projectId);
|
|
4268
|
-
const approvalStateUrl = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4309
|
+
const approvalStateUrl = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/approval_state`);
|
|
4269
4310
|
const approvalStateResponse = await fetch(approvalStateUrl.toString(), {
|
|
4270
4311
|
...getFetchConfig(),
|
|
4271
4312
|
method: "GET",
|
|
@@ -4313,7 +4354,7 @@ async function getMergeRequestApprovalState(projectId, mergeRequestIid) {
|
|
|
4313
4354
|
*/
|
|
4314
4355
|
async function getMergeRequestConflicts(projectId, mergeRequestIid) {
|
|
4315
4356
|
projectId = decodeURIComponent(projectId);
|
|
4316
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4357
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/conflicts`);
|
|
4317
4358
|
const response = await fetch(url.toString(), {
|
|
4318
4359
|
...getFetchConfig(),
|
|
4319
4360
|
method: "GET",
|
|
@@ -4322,7 +4363,7 @@ async function getMergeRequestConflicts(projectId, mergeRequestIid) {
|
|
|
4322
4363
|
return (await response.json());
|
|
4323
4364
|
}
|
|
4324
4365
|
async function getMergeRequestApprovalsFallback(projectId, mergeRequestIid) {
|
|
4325
|
-
const approvalsUrl = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4366
|
+
const approvalsUrl = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/approvals`);
|
|
4326
4367
|
const approvalsResponse = await fetch(approvalsUrl.toString(), {
|
|
4327
4368
|
...getFetchConfig(),
|
|
4328
4369
|
method: "GET",
|
|
@@ -4382,7 +4423,7 @@ async function createNote(projectId, noteableType, // specifies 'issue' or 'merg
|
|
|
4382
4423
|
noteableIid, body) {
|
|
4383
4424
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
4384
4425
|
// ⚙️ Response type can be adjusted according to the GitLab API documentation
|
|
4385
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4426
|
+
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
4427
|
);
|
|
4387
4428
|
const response = await fetch(url.toString(), {
|
|
4388
4429
|
...getFetchConfig(),
|
|
@@ -4403,7 +4444,7 @@ noteableIid, body) {
|
|
|
4403
4444
|
*/
|
|
4404
4445
|
async function getDraftNote(project_id, merge_request_iid, draft_note_id) {
|
|
4405
4446
|
const effectiveProjectId = getEffectiveProjectId(decodeURIComponent(project_id));
|
|
4406
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
4447
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/merge_requests/${encodeGitLabPathSegment(merge_request_iid)}/draft_notes/${encodeGitLabPathSegment(draft_note_id)}`, { ...getFetchConfig() });
|
|
4407
4448
|
if (!response.ok) {
|
|
4408
4449
|
const errorText = await response.text();
|
|
4409
4450
|
throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorText}`);
|
|
@@ -4413,7 +4454,7 @@ async function getDraftNote(project_id, merge_request_iid, draft_note_id) {
|
|
|
4413
4454
|
}
|
|
4414
4455
|
async function listDraftNotes(projectId, mergeRequestIid) {
|
|
4415
4456
|
projectId = decodeURIComponent(projectId);
|
|
4416
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4457
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes`);
|
|
4417
4458
|
const response = await fetch(url.toString(), {
|
|
4418
4459
|
...getFetchConfig(),
|
|
4419
4460
|
method: "GET",
|
|
@@ -4437,7 +4478,7 @@ async function listDraftNotes(projectId, mergeRequestIid) {
|
|
|
4437
4478
|
*/
|
|
4438
4479
|
async function createDraftNote(projectId, mergeRequestIid, body, inReplyToDiscussionId, position, resolveDiscussion) {
|
|
4439
4480
|
projectId = decodeURIComponent(projectId);
|
|
4440
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4481
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes`);
|
|
4441
4482
|
const requestBody = { note: body };
|
|
4442
4483
|
if (inReplyToDiscussionId) {
|
|
4443
4484
|
requestBody.in_reply_to_discussion_id = inReplyToDiscussionId;
|
|
@@ -4472,7 +4513,7 @@ async function createDraftNote(projectId, mergeRequestIid, body, inReplyToDiscus
|
|
|
4472
4513
|
*/
|
|
4473
4514
|
async function updateDraftNote(projectId, mergeRequestIid, draftNoteId, body, position, resolveDiscussion) {
|
|
4474
4515
|
projectId = decodeURIComponent(projectId);
|
|
4475
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4516
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes/${encodeGitLabPathSegment(draftNoteId)}`);
|
|
4476
4517
|
const requestBody = {};
|
|
4477
4518
|
if (body !== undefined) {
|
|
4478
4519
|
requestBody.note = body;
|
|
@@ -4504,7 +4545,7 @@ async function updateDraftNote(projectId, mergeRequestIid, draftNoteId, body, po
|
|
|
4504
4545
|
*/
|
|
4505
4546
|
async function deleteDraftNote(projectId, mergeRequestIid, draftNoteId) {
|
|
4506
4547
|
projectId = decodeURIComponent(projectId);
|
|
4507
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4548
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes/${encodeGitLabPathSegment(draftNoteId)}`);
|
|
4508
4549
|
const response = await fetch(url.toString(), {
|
|
4509
4550
|
...getFetchConfig(),
|
|
4510
4551
|
method: "DELETE",
|
|
@@ -4523,7 +4564,7 @@ async function deleteDraftNote(projectId, mergeRequestIid, draftNoteId) {
|
|
|
4523
4564
|
*/
|
|
4524
4565
|
async function publishDraftNote(projectId, mergeRequestIid, draftNoteId) {
|
|
4525
4566
|
projectId = decodeURIComponent(projectId);
|
|
4526
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4567
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes/${encodeGitLabPathSegment(draftNoteId)}/publish`);
|
|
4527
4568
|
const response = await fetch(url.toString(), {
|
|
4528
4569
|
...getFetchConfig(),
|
|
4529
4570
|
method: "PUT",
|
|
@@ -4594,7 +4635,7 @@ async function fetchGitLabInstanceVersion() {
|
|
|
4594
4635
|
*/
|
|
4595
4636
|
async function bulkPublishDraftNotes(projectId, mergeRequestIid, options = {}) {
|
|
4596
4637
|
projectId = decodeURIComponent(projectId);
|
|
4597
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4638
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/draft_notes/bulk_publish`);
|
|
4598
4639
|
const body = buildBulkPublishDraftNotesBody(options);
|
|
4599
4640
|
if (needsGitLab19_2BulkPublish(body)) {
|
|
4600
4641
|
await assertGitLabVersionAtLeast({
|
|
@@ -4632,7 +4673,7 @@ async function bulkPublishDraftNotes(projectId, mergeRequestIid, options = {}) {
|
|
|
4632
4673
|
}
|
|
4633
4674
|
async function resolveMergeRequestThread(projectId, mergeRequestIid, discussionId, resolved) {
|
|
4634
4675
|
projectId = decodeURIComponent(projectId);
|
|
4635
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4676
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}`);
|
|
4636
4677
|
if (resolved !== undefined) {
|
|
4637
4678
|
url.searchParams.append("resolved", resolved ? "true" : "false");
|
|
4638
4679
|
}
|
|
@@ -4664,7 +4705,7 @@ async function resolveMergeRequestThread(projectId, mergeRequestIid, discussionI
|
|
|
4664
4705
|
*/
|
|
4665
4706
|
async function createMergeRequestThread(projectId, mergeRequestIid, body, position, createdAt) {
|
|
4666
4707
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
4667
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4708
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions`);
|
|
4668
4709
|
const payload = { body };
|
|
4669
4710
|
if (position) {
|
|
4670
4711
|
payload.position = position;
|
|
@@ -4691,7 +4732,7 @@ async function createMergeRequestThread(projectId, mergeRequestIid, body, positi
|
|
|
4691
4732
|
*/
|
|
4692
4733
|
async function listMergeRequestVersions(projectId, mergeRequestIid) {
|
|
4693
4734
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
4694
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4735
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/versions`);
|
|
4695
4736
|
const response = await fetch(url.toString(), {
|
|
4696
4737
|
...getFetchConfig(),
|
|
4697
4738
|
});
|
|
@@ -4710,7 +4751,7 @@ async function listMergeRequestVersions(projectId, mergeRequestIid) {
|
|
|
4710
4751
|
*/
|
|
4711
4752
|
async function getMergeRequestVersion(projectId, mergeRequestIid, versionId, unidiff) {
|
|
4712
4753
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
4713
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4754
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/versions/${encodeGitLabPathSegment(versionId)}`);
|
|
4714
4755
|
if (unidiff !== undefined) {
|
|
4715
4756
|
url.searchParams.append("unidiff", String(unidiff));
|
|
4716
4757
|
}
|
|
@@ -4752,7 +4793,7 @@ async function listProjects(options = {}) {
|
|
|
4752
4793
|
async function listLabels(projectId, options = {}) {
|
|
4753
4794
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
4754
4795
|
// Construct the URL with project path
|
|
4755
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4796
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/labels`);
|
|
4756
4797
|
// Add query parameters
|
|
4757
4798
|
appendFilterParams(url.searchParams, options);
|
|
4758
4799
|
// Make the API request
|
|
@@ -4775,7 +4816,7 @@ async function listLabels(projectId, options = {}) {
|
|
|
4775
4816
|
*/
|
|
4776
4817
|
async function getLabel(projectId, labelId, includeAncestorGroups) {
|
|
4777
4818
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
4778
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
4819
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/labels/${encodeGitLabPathSegment(String(labelId))}`);
|
|
4779
4820
|
// Add query parameters
|
|
4780
4821
|
if (includeAncestorGroups !== undefined) {
|
|
4781
4822
|
url.searchParams.append("include_ancestor_groups", includeAncestorGroups ? "true" : "false");
|
|
@@ -4800,7 +4841,7 @@ async function getLabel(projectId, labelId, includeAncestorGroups) {
|
|
|
4800
4841
|
async function createLabel(projectId, options) {
|
|
4801
4842
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
4802
4843
|
// Make the API request
|
|
4803
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
4844
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/labels`, {
|
|
4804
4845
|
...getFetchConfig(),
|
|
4805
4846
|
method: "POST",
|
|
4806
4847
|
body: JSON.stringify(options),
|
|
@@ -4822,7 +4863,7 @@ async function createLabel(projectId, options) {
|
|
|
4822
4863
|
async function updateLabel(projectId, labelId, options) {
|
|
4823
4864
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
4824
4865
|
// Make the API request
|
|
4825
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
4866
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/labels/${encodeGitLabPathSegment(String(labelId))}`, {
|
|
4826
4867
|
...getFetchConfig(),
|
|
4827
4868
|
method: "PUT",
|
|
4828
4869
|
body: JSON.stringify(options),
|
|
@@ -4842,7 +4883,7 @@ async function updateLabel(projectId, labelId, options) {
|
|
|
4842
4883
|
async function deleteLabel(projectId, labelId) {
|
|
4843
4884
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
4844
4885
|
// Make the API request
|
|
4845
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
4886
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/labels/${encodeGitLabPathSegment(String(labelId))}`, {
|
|
4846
4887
|
...getFetchConfig(),
|
|
4847
4888
|
method: "DELETE",
|
|
4848
4889
|
});
|
|
@@ -4856,7 +4897,7 @@ async function deleteLabel(projectId, labelId) {
|
|
|
4856
4897
|
* @returns {Promise<GitLabProject[]>} Array of projects in the group
|
|
4857
4898
|
*/
|
|
4858
4899
|
async function listGroupProjects(options) {
|
|
4859
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${
|
|
4900
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(options.group_id)}/projects`);
|
|
4860
4901
|
// Add optional parameters to URL
|
|
4861
4902
|
if (options.include_subgroups)
|
|
4862
4903
|
url.searchParams.append("include_subgroups", "true");
|
|
@@ -4906,11 +4947,11 @@ async function listGroupProjects(options) {
|
|
|
4906
4947
|
function buildWebhookBaseUrl(projectId, groupId) {
|
|
4907
4948
|
if (projectId) {
|
|
4908
4949
|
projectId = decodeURIComponent(projectId);
|
|
4909
|
-
return `${getEffectiveApiUrl()}/projects/${
|
|
4950
|
+
return `${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/hooks`;
|
|
4910
4951
|
}
|
|
4911
4952
|
rejectIfStrictProjectScope("group webhooks");
|
|
4912
4953
|
const decodedGroupId = decodeURIComponent(groupId);
|
|
4913
|
-
return `${getEffectiveApiUrl()}/groups/${
|
|
4954
|
+
return `${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(decodedGroupId)}/hooks`;
|
|
4914
4955
|
}
|
|
4915
4956
|
/**
|
|
4916
4957
|
* List webhooks for a project or group
|
|
@@ -5025,7 +5066,7 @@ async function getWebhookEvent(options) {
|
|
|
5025
5066
|
*/
|
|
5026
5067
|
async function listWikiPages(projectId, options = {}) {
|
|
5027
5068
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5028
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5069
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/wikis`);
|
|
5029
5070
|
if (options.page)
|
|
5030
5071
|
url.searchParams.append("page", options.page.toString());
|
|
5031
5072
|
if (options.per_page)
|
|
@@ -5046,7 +5087,7 @@ async function listWikiPages(projectId, options = {}) {
|
|
|
5046
5087
|
*/
|
|
5047
5088
|
async function getWikiPage(projectId, slug, renderHtml) {
|
|
5048
5089
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5049
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5090
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/wikis/${encodeGitLabPathSegment(slug)}`);
|
|
5050
5091
|
if (renderHtml)
|
|
5051
5092
|
url.searchParams.append("render_html", renderHtml.toString());
|
|
5052
5093
|
const response = await fetch(url.toString(), { ...getFetchConfig() });
|
|
@@ -5062,7 +5103,7 @@ async function createWikiPage(projectId, title, content, format) {
|
|
|
5062
5103
|
const body = { title, content };
|
|
5063
5104
|
if (format)
|
|
5064
5105
|
body.format = format;
|
|
5065
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
5106
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/wikis`, {
|
|
5066
5107
|
...getFetchConfig(),
|
|
5067
5108
|
method: "POST",
|
|
5068
5109
|
body: JSON.stringify(body),
|
|
@@ -5090,7 +5131,7 @@ async function updateWikiPage(projectId, slug, title, content, format) {
|
|
|
5090
5131
|
body.content = content;
|
|
5091
5132
|
if (format)
|
|
5092
5133
|
body.format = format;
|
|
5093
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
5134
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/wikis/${encodeGitLabPathSegment(slug)}`, {
|
|
5094
5135
|
...getFetchConfig(),
|
|
5095
5136
|
method: "PUT",
|
|
5096
5137
|
body: JSON.stringify(body),
|
|
@@ -5104,7 +5145,7 @@ async function updateWikiPage(projectId, slug, title, content, format) {
|
|
|
5104
5145
|
*/
|
|
5105
5146
|
async function deleteWikiPage(projectId, slug) {
|
|
5106
5147
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5107
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
5148
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/wikis/${encodeGitLabPathSegment(slug)}`, {
|
|
5108
5149
|
...getFetchConfig(),
|
|
5109
5150
|
method: "DELETE",
|
|
5110
5151
|
});
|
|
@@ -5115,7 +5156,7 @@ async function deleteWikiPage(projectId, slug) {
|
|
|
5115
5156
|
*/
|
|
5116
5157
|
async function listGroupWikiPages(groupId, options = {}) {
|
|
5117
5158
|
groupId = decodeURIComponent(groupId); // Decode group ID
|
|
5118
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${
|
|
5159
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/wikis`);
|
|
5119
5160
|
if (options.page)
|
|
5120
5161
|
url.searchParams.append("page", options.page.toString());
|
|
5121
5162
|
if (options.per_page)
|
|
@@ -5136,7 +5177,7 @@ async function listGroupWikiPages(groupId, options = {}) {
|
|
|
5136
5177
|
*/
|
|
5137
5178
|
async function getGroupWikiPage(groupId, slug, renderHtml) {
|
|
5138
5179
|
groupId = decodeURIComponent(groupId); // Decode group ID
|
|
5139
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${
|
|
5180
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/wikis/${encodeGitLabPathSegment(slug)}`);
|
|
5140
5181
|
if (renderHtml)
|
|
5141
5182
|
url.searchParams.append("render_html", renderHtml.toString());
|
|
5142
5183
|
const response = await fetch(url.toString(), { ...getFetchConfig() });
|
|
@@ -5152,7 +5193,7 @@ async function createGroupWikiPage(groupId, title, content, format) {
|
|
|
5152
5193
|
const body = { title, content };
|
|
5153
5194
|
if (format)
|
|
5154
5195
|
body.format = format;
|
|
5155
|
-
const response = await fetch(`${getEffectiveApiUrl()}/groups/${
|
|
5196
|
+
const response = await fetch(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/wikis`, {
|
|
5156
5197
|
...getFetchConfig(),
|
|
5157
5198
|
method: "POST",
|
|
5158
5199
|
body: JSON.stringify(body),
|
|
@@ -5180,7 +5221,7 @@ async function updateGroupWikiPage(groupId, slug, title, content, format) {
|
|
|
5180
5221
|
body.content = content;
|
|
5181
5222
|
if (format)
|
|
5182
5223
|
body.format = format;
|
|
5183
|
-
const response = await fetch(`${getEffectiveApiUrl()}/groups/${
|
|
5224
|
+
const response = await fetch(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/wikis/${encodeGitLabPathSegment(slug)}`, {
|
|
5184
5225
|
...getFetchConfig(),
|
|
5185
5226
|
method: "PUT",
|
|
5186
5227
|
body: JSON.stringify(body),
|
|
@@ -5194,7 +5235,7 @@ async function updateGroupWikiPage(groupId, slug, title, content, format) {
|
|
|
5194
5235
|
*/
|
|
5195
5236
|
async function deleteGroupWikiPage(groupId, slug) {
|
|
5196
5237
|
groupId = decodeURIComponent(groupId); // Decode group ID
|
|
5197
|
-
const response = await fetch(`${getEffectiveApiUrl()}/groups/${
|
|
5238
|
+
const response = await fetch(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/wikis/${encodeGitLabPathSegment(slug)}`, {
|
|
5198
5239
|
...getFetchConfig(),
|
|
5199
5240
|
method: "DELETE",
|
|
5200
5241
|
});
|
|
@@ -5209,7 +5250,7 @@ async function deleteGroupWikiPage(groupId, slug) {
|
|
|
5209
5250
|
*/
|
|
5210
5251
|
async function listPipelines(projectId, options = {}) {
|
|
5211
5252
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5212
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5253
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines`);
|
|
5213
5254
|
// Add all query parameters
|
|
5214
5255
|
appendFilterParams(url.searchParams, options);
|
|
5215
5256
|
const response = await fetch(url.toString(), {
|
|
@@ -5228,7 +5269,7 @@ async function listPipelines(projectId, options = {}) {
|
|
|
5228
5269
|
*/
|
|
5229
5270
|
async function getPipeline(projectId, pipelineId, signal) {
|
|
5230
5271
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5231
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5272
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}`);
|
|
5232
5273
|
const response = await fetch(url.toString(), {
|
|
5233
5274
|
...getFetchConfig(),
|
|
5234
5275
|
signal,
|
|
@@ -5242,7 +5283,7 @@ async function getPipeline(projectId, pipelineId, signal) {
|
|
|
5242
5283
|
}
|
|
5243
5284
|
async function getPipelineVariables(projectId, pipelineId, page, perPage) {
|
|
5244
5285
|
projectId = decodeURIComponent(projectId);
|
|
5245
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5286
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/variables`);
|
|
5246
5287
|
if (page !== undefined)
|
|
5247
5288
|
url.searchParams.set("page", String(page));
|
|
5248
5289
|
if (perPage !== undefined)
|
|
@@ -5254,7 +5295,7 @@ async function getPipelineVariables(projectId, pipelineId, page, perPage) {
|
|
|
5254
5295
|
async function getPipelineReport(projectId, pipelineId, summary = false, page, perPage) {
|
|
5255
5296
|
projectId = decodeURIComponent(projectId);
|
|
5256
5297
|
const endpoint = summary ? "test_report_summary" : "test_report";
|
|
5257
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5298
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/${endpoint}`);
|
|
5258
5299
|
if (page !== undefined)
|
|
5259
5300
|
url.searchParams.set("page", String(page));
|
|
5260
5301
|
if (perPage !== undefined)
|
|
@@ -5265,13 +5306,13 @@ async function getPipelineReport(projectId, pipelineId, summary = false, page, p
|
|
|
5265
5306
|
}
|
|
5266
5307
|
async function deletePipeline(projectId, pipelineId) {
|
|
5267
5308
|
projectId = decodeURIComponent(projectId);
|
|
5268
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
5309
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}`, { ...getFetchConfig(), method: "DELETE" });
|
|
5269
5310
|
await handleGitLabError(response);
|
|
5270
5311
|
return response.status === 204 ? { deleted: true } : response.json();
|
|
5271
5312
|
}
|
|
5272
5313
|
async function updatePipelineMetadata(projectId, pipelineId, name) {
|
|
5273
5314
|
projectId = decodeURIComponent(projectId);
|
|
5274
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
5315
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/metadata`, { ...getFetchConfig(), method: "PUT", body: JSON.stringify({ name }) });
|
|
5275
5316
|
await handleGitLabError(response);
|
|
5276
5317
|
return response.json();
|
|
5277
5318
|
}
|
|
@@ -5284,7 +5325,7 @@ async function updatePipelineMetadata(projectId, pipelineId, name) {
|
|
|
5284
5325
|
*/
|
|
5285
5326
|
async function listDeployments(projectId, options = {}) {
|
|
5286
5327
|
projectId = decodeURIComponent(projectId);
|
|
5287
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5328
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/deployments`);
|
|
5288
5329
|
appendFilterParams(url.searchParams, options);
|
|
5289
5330
|
const response = await fetch(url.toString(), {
|
|
5290
5331
|
...getFetchConfig(),
|
|
@@ -5302,7 +5343,7 @@ async function listDeployments(projectId, options = {}) {
|
|
|
5302
5343
|
*/
|
|
5303
5344
|
async function getDeployment(projectId, deploymentId) {
|
|
5304
5345
|
projectId = decodeURIComponent(projectId);
|
|
5305
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5346
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/deployments/${encodeGitLabPathSegment(deploymentId)}`);
|
|
5306
5347
|
const response = await fetch(url.toString(), {
|
|
5307
5348
|
...getFetchConfig(),
|
|
5308
5349
|
});
|
|
@@ -5315,7 +5356,7 @@ async function getDeployment(projectId, deploymentId) {
|
|
|
5315
5356
|
}
|
|
5316
5357
|
async function deploymentRequest(projectId, path = "", method = "GET", body, query) {
|
|
5317
5358
|
projectId = decodeURIComponent(projectId);
|
|
5318
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5359
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/deployments${path}`);
|
|
5319
5360
|
for (const [key, value] of Object.entries(query ?? {})) {
|
|
5320
5361
|
if (value !== undefined)
|
|
5321
5362
|
url.searchParams.set(key, String(value));
|
|
@@ -5336,7 +5377,7 @@ async function deploymentRequest(projectId, path = "", method = "GET", body, que
|
|
|
5336
5377
|
*/
|
|
5337
5378
|
async function listEnvironments(projectId, options = {}) {
|
|
5338
5379
|
projectId = decodeURIComponent(projectId);
|
|
5339
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5380
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/environments`);
|
|
5340
5381
|
appendFilterParams(url.searchParams, options);
|
|
5341
5382
|
const response = await fetch(url.toString(), {
|
|
5342
5383
|
...getFetchConfig(),
|
|
@@ -5354,7 +5395,7 @@ async function listEnvironments(projectId, options = {}) {
|
|
|
5354
5395
|
*/
|
|
5355
5396
|
async function getEnvironment(projectId, environmentId) {
|
|
5356
5397
|
projectId = decodeURIComponent(projectId);
|
|
5357
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5398
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/environments/${encodeGitLabPathSegment(environmentId)}`);
|
|
5358
5399
|
const response = await fetch(url.toString(), {
|
|
5359
5400
|
...getFetchConfig(),
|
|
5360
5401
|
});
|
|
@@ -5367,7 +5408,7 @@ async function getEnvironment(projectId, environmentId) {
|
|
|
5367
5408
|
}
|
|
5368
5409
|
async function environmentRequest(projectId, path, method, body, query) {
|
|
5369
5410
|
projectId = decodeURIComponent(projectId);
|
|
5370
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5411
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/environments${path}`);
|
|
5371
5412
|
for (const [key, value] of Object.entries(query ?? {}))
|
|
5372
5413
|
if (value !== undefined)
|
|
5373
5414
|
url.searchParams.set(key, String(value));
|
|
@@ -5377,13 +5418,13 @@ async function environmentRequest(projectId, path, method, body, query) {
|
|
|
5377
5418
|
}
|
|
5378
5419
|
async function pipelineTriggerRequest(projectId, path = "", method = "GET", body) {
|
|
5379
5420
|
projectId = decodeURIComponent(projectId);
|
|
5380
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
5421
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/triggers${path}`, { ...getFetchConfig(), method, body: body === undefined ? undefined : JSON.stringify(body) });
|
|
5381
5422
|
await handleGitLabError(response);
|
|
5382
5423
|
return response.status === 204 ? { success: true } : response.json();
|
|
5383
5424
|
}
|
|
5384
5425
|
async function triggerPipeline(projectId, token, ref, variables, inputs) {
|
|
5385
5426
|
projectId = decodeURIComponent(projectId);
|
|
5386
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5427
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/trigger/pipeline`);
|
|
5387
5428
|
url.searchParams.set("token", token);
|
|
5388
5429
|
url.searchParams.set("ref", ref);
|
|
5389
5430
|
const body = {};
|
|
@@ -5405,7 +5446,7 @@ async function triggerPipeline(projectId, token, ref, variables, inputs) {
|
|
|
5405
5446
|
*/
|
|
5406
5447
|
async function listPipelineJobs(projectId, pipelineId, options = {}) {
|
|
5407
5448
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5408
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5449
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/jobs`);
|
|
5409
5450
|
// Add all query parameters
|
|
5410
5451
|
appendFilterParams(url.searchParams, options);
|
|
5411
5452
|
const response = await fetch(url.toString(), {
|
|
@@ -5428,7 +5469,7 @@ async function listPipelineJobs(projectId, pipelineId, options = {}) {
|
|
|
5428
5469
|
*/
|
|
5429
5470
|
async function listPipelineTriggerJobs(projectId, pipelineId, options = {}) {
|
|
5430
5471
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5431
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5472
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/bridges`);
|
|
5432
5473
|
// Add all query parameters
|
|
5433
5474
|
appendFilterParams(url.searchParams, options);
|
|
5434
5475
|
const response = await fetch(url.toString(), {
|
|
@@ -5443,7 +5484,7 @@ async function listPipelineTriggerJobs(projectId, pipelineId, options = {}) {
|
|
|
5443
5484
|
}
|
|
5444
5485
|
async function getPipelineJob(projectId, jobId, signal) {
|
|
5445
5486
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5446
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5487
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}`);
|
|
5447
5488
|
const response = await fetch(url.toString(), {
|
|
5448
5489
|
...getFetchConfig(),
|
|
5449
5490
|
signal,
|
|
@@ -5467,7 +5508,7 @@ async function getPipelineJob(projectId, jobId, signal) {
|
|
|
5467
5508
|
const MAX_JOB_TRACE_LINES = 1000;
|
|
5468
5509
|
async function getPipelineJobOutput(projectId, jobId, limit, offset) {
|
|
5469
5510
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5470
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5511
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/trace`);
|
|
5471
5512
|
const response = await fetch(url.toString(), {
|
|
5472
5513
|
...getFetchConfig(),
|
|
5473
5514
|
headers: {
|
|
@@ -5502,7 +5543,7 @@ async function getPipelineJobOutput(projectId, jobId, limit, offset) {
|
|
|
5502
5543
|
}
|
|
5503
5544
|
async function validateCiLint(projectId, options) {
|
|
5504
5545
|
projectId = decodeURIComponent(projectId);
|
|
5505
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5546
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/ci/lint`);
|
|
5506
5547
|
const response = await fetch(url.toString(), {
|
|
5507
5548
|
...getFetchConfig(),
|
|
5508
5549
|
method: "POST",
|
|
@@ -5514,7 +5555,7 @@ async function validateCiLint(projectId, options) {
|
|
|
5514
5555
|
}
|
|
5515
5556
|
async function validateProjectCiLint(projectId, options) {
|
|
5516
5557
|
projectId = decodeURIComponent(projectId);
|
|
5517
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5558
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/ci/lint`);
|
|
5518
5559
|
appendFilterParams(url.searchParams, options);
|
|
5519
5560
|
const response = await fetch(url.toString(), {
|
|
5520
5561
|
...getFetchConfig(),
|
|
@@ -5533,7 +5574,7 @@ async function validateProjectCiLint(projectId, options) {
|
|
|
5533
5574
|
*/
|
|
5534
5575
|
async function listJobArtifacts(projectId, jobId, options = {}) {
|
|
5535
5576
|
projectId = decodeURIComponent(projectId);
|
|
5536
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5577
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/artifacts/tree`);
|
|
5537
5578
|
appendFilterParams(url.searchParams, options);
|
|
5538
5579
|
const response = await fetch(url.toString(), {
|
|
5539
5580
|
...getFetchConfig(),
|
|
@@ -5556,10 +5597,8 @@ async function listJobArtifacts(projectId, jobId, options = {}) {
|
|
|
5556
5597
|
async function downloadJobArtifacts(projectId, jobId, localPath) {
|
|
5557
5598
|
projectId = decodeURIComponent(projectId);
|
|
5558
5599
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
5559
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5560
|
-
const response = await
|
|
5561
|
-
...getFetchConfig(),
|
|
5562
|
-
});
|
|
5600
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/jobs/${encodeGitLabPathSegment(jobId)}/artifacts`);
|
|
5601
|
+
const response = await fetchWithValidatedRedirects(url.toString(), downloadRedirectOptions());
|
|
5563
5602
|
if (response.status === 404) {
|
|
5564
5603
|
throw new Error(`Job artifacts not found. The job may not have produced artifacts or the job ID is invalid.`);
|
|
5565
5604
|
}
|
|
@@ -5583,14 +5622,9 @@ async function downloadJobArtifacts(projectId, jobId, localPath) {
|
|
|
5583
5622
|
async function getJobArtifactFile(projectId, jobId, artifactPath) {
|
|
5584
5623
|
projectId = decodeURIComponent(projectId);
|
|
5585
5624
|
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
|
-
});
|
|
5625
|
+
const encodedArtifactPath = encodeGitLabPath(artifactPath);
|
|
5626
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/jobs/${encodeGitLabPathSegment(jobId)}/artifacts/${encodedArtifactPath}`);
|
|
5627
|
+
const response = await fetchWithValidatedRedirects(url.toString(), downloadRedirectOptions());
|
|
5594
5628
|
if (response.status === 404) {
|
|
5595
5629
|
throw new Error(`Artifact file not found: ${artifactPath}`);
|
|
5596
5630
|
}
|
|
@@ -5608,7 +5642,7 @@ async function getJobArtifactFile(projectId, jobId, artifactPath) {
|
|
|
5608
5642
|
*/
|
|
5609
5643
|
async function createPipeline(projectId, ref, variables, inputs) {
|
|
5610
5644
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5611
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5645
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline`);
|
|
5612
5646
|
const body = { ref };
|
|
5613
5647
|
if (variables && variables.length > 0) {
|
|
5614
5648
|
body.variables = variables;
|
|
@@ -5634,7 +5668,7 @@ async function createPipeline(projectId, ref, variables, inputs) {
|
|
|
5634
5668
|
*/
|
|
5635
5669
|
async function retryPipeline(projectId, pipelineId) {
|
|
5636
5670
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5637
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5671
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/retry`);
|
|
5638
5672
|
const response = await fetch(url.toString(), {
|
|
5639
5673
|
...getFetchConfig(),
|
|
5640
5674
|
method: "POST",
|
|
@@ -5652,7 +5686,7 @@ async function retryPipeline(projectId, pipelineId) {
|
|
|
5652
5686
|
*/
|
|
5653
5687
|
async function cancelPipeline(projectId, pipelineId) {
|
|
5654
5688
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5655
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5689
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/cancel`);
|
|
5656
5690
|
const response = await fetch(url.toString(), {
|
|
5657
5691
|
...getFetchConfig(),
|
|
5658
5692
|
method: "POST",
|
|
@@ -5670,7 +5704,7 @@ async function cancelPipeline(projectId, pipelineId) {
|
|
|
5670
5704
|
*/
|
|
5671
5705
|
async function listPipelineSchedules(projectId, options = {}) {
|
|
5672
5706
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5673
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5707
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules`);
|
|
5674
5708
|
appendFilterParams(url.searchParams, options);
|
|
5675
5709
|
const response = await fetch(url.toString(), {
|
|
5676
5710
|
...getFetchConfig(),
|
|
@@ -5688,7 +5722,7 @@ async function listPipelineSchedules(projectId, options = {}) {
|
|
|
5688
5722
|
*/
|
|
5689
5723
|
async function getPipelineSchedule(projectId, pipelineScheduleId) {
|
|
5690
5724
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5691
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5725
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}`);
|
|
5692
5726
|
const response = await fetch(url.toString(), {
|
|
5693
5727
|
...getFetchConfig(),
|
|
5694
5728
|
});
|
|
@@ -5706,7 +5740,7 @@ async function getPipelineSchedule(projectId, pipelineScheduleId) {
|
|
|
5706
5740
|
*/
|
|
5707
5741
|
async function listPipelineSchedulePipelines(projectId, pipelineScheduleId, options = {}) {
|
|
5708
5742
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5709
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5743
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/pipelines`);
|
|
5710
5744
|
appendFilterParams(url.searchParams, options);
|
|
5711
5745
|
const response = await fetch(url.toString(), {
|
|
5712
5746
|
...getFetchConfig(),
|
|
@@ -5724,7 +5758,7 @@ async function listPipelineSchedulePipelines(projectId, pipelineScheduleId, opti
|
|
|
5724
5758
|
*/
|
|
5725
5759
|
async function createPipelineSchedule(projectId, options) {
|
|
5726
5760
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5727
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5761
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules`);
|
|
5728
5762
|
const response = await fetch(url.toString(), {
|
|
5729
5763
|
...getFetchConfig(),
|
|
5730
5764
|
method: "POST",
|
|
@@ -5744,7 +5778,7 @@ async function createPipelineSchedule(projectId, options) {
|
|
|
5744
5778
|
*/
|
|
5745
5779
|
async function updatePipelineSchedule(projectId, pipelineScheduleId, options) {
|
|
5746
5780
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5747
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5781
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}`);
|
|
5748
5782
|
const response = await fetch(url.toString(), {
|
|
5749
5783
|
...getFetchConfig(),
|
|
5750
5784
|
method: "PUT",
|
|
@@ -5763,7 +5797,7 @@ async function updatePipelineSchedule(projectId, pipelineScheduleId, options) {
|
|
|
5763
5797
|
*/
|
|
5764
5798
|
async function deletePipelineSchedule(projectId, pipelineScheduleId) {
|
|
5765
5799
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5766
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5800
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}`);
|
|
5767
5801
|
const response = await fetch(url.toString(), {
|
|
5768
5802
|
...getFetchConfig(),
|
|
5769
5803
|
method: "DELETE",
|
|
@@ -5779,7 +5813,7 @@ async function deletePipelineSchedule(projectId, pipelineScheduleId) {
|
|
|
5779
5813
|
*/
|
|
5780
5814
|
async function playPipelineSchedule(projectId, pipelineScheduleId) {
|
|
5781
5815
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5782
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5816
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/play`);
|
|
5783
5817
|
const response = await fetch(url.toString(), {
|
|
5784
5818
|
...getFetchConfig(),
|
|
5785
5819
|
method: "POST",
|
|
@@ -5797,7 +5831,7 @@ async function playPipelineSchedule(projectId, pipelineScheduleId) {
|
|
|
5797
5831
|
*/
|
|
5798
5832
|
async function takeOwnershipPipelineSchedule(projectId, pipelineScheduleId) {
|
|
5799
5833
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5800
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5834
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/take_ownership`);
|
|
5801
5835
|
const response = await fetch(url.toString(), {
|
|
5802
5836
|
...getFetchConfig(),
|
|
5803
5837
|
method: "POST",
|
|
@@ -5816,7 +5850,7 @@ async function takeOwnershipPipelineSchedule(projectId, pipelineScheduleId) {
|
|
|
5816
5850
|
*/
|
|
5817
5851
|
async function getPipelineScheduleVariable(projectId, pipelineScheduleId, key) {
|
|
5818
5852
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5819
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5853
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/variables/${encodeGitLabPathSegment(key)}`);
|
|
5820
5854
|
const response = await fetch(url.toString(), {
|
|
5821
5855
|
...getFetchConfig(),
|
|
5822
5856
|
});
|
|
@@ -5834,7 +5868,7 @@ async function getPipelineScheduleVariable(projectId, pipelineScheduleId, key) {
|
|
|
5834
5868
|
*/
|
|
5835
5869
|
async function createPipelineScheduleVariable(projectId, pipelineScheduleId, options) {
|
|
5836
5870
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5837
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5871
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/variables`);
|
|
5838
5872
|
const response = await fetch(url.toString(), {
|
|
5839
5873
|
...getFetchConfig(),
|
|
5840
5874
|
method: "POST",
|
|
@@ -5855,7 +5889,7 @@ async function createPipelineScheduleVariable(projectId, pipelineScheduleId, opt
|
|
|
5855
5889
|
*/
|
|
5856
5890
|
async function updatePipelineScheduleVariable(projectId, pipelineScheduleId, key, options) {
|
|
5857
5891
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5858
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5892
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/variables/${encodeGitLabPathSegment(key)}`);
|
|
5859
5893
|
const response = await fetch(url.toString(), {
|
|
5860
5894
|
...getFetchConfig(),
|
|
5861
5895
|
method: "PUT",
|
|
@@ -5875,7 +5909,7 @@ async function updatePipelineScheduleVariable(projectId, pipelineScheduleId, key
|
|
|
5875
5909
|
*/
|
|
5876
5910
|
async function deletePipelineScheduleVariable(projectId, pipelineScheduleId, key) {
|
|
5877
5911
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5878
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5912
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/variables/${encodeGitLabPathSegment(key)}`);
|
|
5879
5913
|
const response = await fetch(url.toString(), {
|
|
5880
5914
|
...getFetchConfig(),
|
|
5881
5915
|
method: "DELETE",
|
|
@@ -5892,7 +5926,7 @@ async function deletePipelineScheduleVariable(projectId, pipelineScheduleId, key
|
|
|
5892
5926
|
*/
|
|
5893
5927
|
async function playPipelineJob(projectId, jobId, variables, jobInputs) {
|
|
5894
5928
|
projectId = decodeURIComponent(projectId);
|
|
5895
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5929
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/play`);
|
|
5896
5930
|
const body = {};
|
|
5897
5931
|
if (variables && variables.length > 0) {
|
|
5898
5932
|
body.job_variables_attributes = variables;
|
|
@@ -5926,7 +5960,7 @@ async function playPipelineJobs(projectId, jobIds, variables, timeoutSeconds = 3
|
|
|
5926
5960
|
*/
|
|
5927
5961
|
async function retryPipelineJob(projectId, jobId, jobInputs) {
|
|
5928
5962
|
projectId = decodeURIComponent(projectId);
|
|
5929
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
5963
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/retry`);
|
|
5930
5964
|
const response = await fetch(url.toString(), {
|
|
5931
5965
|
...getFetchConfig(),
|
|
5932
5966
|
method: "POST",
|
|
@@ -5938,7 +5972,7 @@ async function retryPipelineJob(projectId, jobId, jobInputs) {
|
|
|
5938
5972
|
}
|
|
5939
5973
|
async function erasePipelineJob(projectId, jobId) {
|
|
5940
5974
|
projectId = decodeURIComponent(projectId);
|
|
5941
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
5975
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/erase`, { ...getFetchConfig(), method: "POST" });
|
|
5942
5976
|
await handleGitLabError(response);
|
|
5943
5977
|
return response.status === 204 ? { erased: true } : response.json();
|
|
5944
5978
|
}
|
|
@@ -5974,7 +6008,7 @@ async function waitForStatus(fetchStatus, terminalStatuses, timeoutSeconds = 300
|
|
|
5974
6008
|
*/
|
|
5975
6009
|
async function cancelPipelineJob(projectId, jobId, force) {
|
|
5976
6010
|
projectId = decodeURIComponent(projectId);
|
|
5977
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6011
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/cancel`);
|
|
5978
6012
|
if (force !== undefined) {
|
|
5979
6013
|
url.searchParams.append("force", force.toString());
|
|
5980
6014
|
}
|
|
@@ -6006,7 +6040,7 @@ async function getRepositoryTree(options) {
|
|
|
6006
6040
|
queryParams.append("page_token", options.page_token);
|
|
6007
6041
|
if (options.pagination)
|
|
6008
6042
|
queryParams.append("pagination", options.pagination);
|
|
6009
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
6043
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(options.project_id))}/repository/tree?${queryParams.toString()}`, { ...getFetchConfig() });
|
|
6010
6044
|
if (response.status === 404) {
|
|
6011
6045
|
throw new Error("Repository or path not found");
|
|
6012
6046
|
}
|
|
@@ -6028,7 +6062,7 @@ async function getRepositoryTree(options) {
|
|
|
6028
6062
|
*/
|
|
6029
6063
|
async function listProjectMilestones(projectId, options) {
|
|
6030
6064
|
projectId = decodeURIComponent(projectId);
|
|
6031
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6065
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones`);
|
|
6032
6066
|
Object.entries(options).forEach(([key, value]) => {
|
|
6033
6067
|
// Drop blank entries inside arrays so they behave like the scalar blank guard below
|
|
6034
6068
|
const normalized = dropBlankArrayEntries(value);
|
|
@@ -6059,7 +6093,7 @@ async function listProjectMilestones(projectId, options) {
|
|
|
6059
6093
|
*/
|
|
6060
6094
|
async function getProjectMilestone(projectId, milestoneId) {
|
|
6061
6095
|
projectId = decodeURIComponent(projectId);
|
|
6062
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6096
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
|
|
6063
6097
|
const response = await fetch(url.toString(), {
|
|
6064
6098
|
...getFetchConfig(),
|
|
6065
6099
|
});
|
|
@@ -6075,7 +6109,7 @@ async function getProjectMilestone(projectId, milestoneId) {
|
|
|
6075
6109
|
*/
|
|
6076
6110
|
async function createProjectMilestone(projectId, options) {
|
|
6077
6111
|
projectId = decodeURIComponent(projectId);
|
|
6078
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6112
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones`);
|
|
6079
6113
|
const response = await fetch(url.toString(), {
|
|
6080
6114
|
...getFetchConfig(),
|
|
6081
6115
|
method: "POST",
|
|
@@ -6094,7 +6128,7 @@ async function createProjectMilestone(projectId, options) {
|
|
|
6094
6128
|
*/
|
|
6095
6129
|
async function editProjectMilestone(projectId, milestoneId, options) {
|
|
6096
6130
|
projectId = decodeURIComponent(projectId);
|
|
6097
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6131
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
|
|
6098
6132
|
const response = await fetch(url.toString(), {
|
|
6099
6133
|
...getFetchConfig(),
|
|
6100
6134
|
method: "PUT",
|
|
@@ -6112,7 +6146,7 @@ async function editProjectMilestone(projectId, milestoneId, options) {
|
|
|
6112
6146
|
*/
|
|
6113
6147
|
async function deleteProjectMilestone(projectId, milestoneId) {
|
|
6114
6148
|
projectId = decodeURIComponent(projectId);
|
|
6115
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6149
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
|
|
6116
6150
|
const response = await fetch(url.toString(), {
|
|
6117
6151
|
...getFetchConfig(),
|
|
6118
6152
|
method: "DELETE",
|
|
@@ -6127,7 +6161,7 @@ async function deleteProjectMilestone(projectId, milestoneId) {
|
|
|
6127
6161
|
*/
|
|
6128
6162
|
async function getMilestoneIssues(projectId, milestoneId, options = {}) {
|
|
6129
6163
|
projectId = decodeURIComponent(projectId);
|
|
6130
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6164
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/issues`);
|
|
6131
6165
|
appendFilterParams(url.searchParams, options);
|
|
6132
6166
|
const response = await fetch(url.toString(), {
|
|
6133
6167
|
...getFetchConfig(),
|
|
@@ -6144,7 +6178,7 @@ async function getMilestoneIssues(projectId, milestoneId, options = {}) {
|
|
|
6144
6178
|
*/
|
|
6145
6179
|
async function getMilestoneMergeRequests(projectId, milestoneId, options = {}) {
|
|
6146
6180
|
projectId = decodeURIComponent(projectId);
|
|
6147
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6181
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/merge_requests`);
|
|
6148
6182
|
appendFilterParams(url.searchParams, options);
|
|
6149
6183
|
const response = await fetch(url.toString(), {
|
|
6150
6184
|
...getFetchConfig(),
|
|
@@ -6161,7 +6195,7 @@ async function getMilestoneMergeRequests(projectId, milestoneId, options = {}) {
|
|
|
6161
6195
|
*/
|
|
6162
6196
|
async function promoteProjectMilestone(projectId, milestoneId) {
|
|
6163
6197
|
projectId = decodeURIComponent(projectId);
|
|
6164
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6198
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/promote`);
|
|
6165
6199
|
const response = await fetch(url.toString(), {
|
|
6166
6200
|
...getFetchConfig(),
|
|
6167
6201
|
method: "POST",
|
|
@@ -6178,7 +6212,7 @@ async function promoteProjectMilestone(projectId, milestoneId) {
|
|
|
6178
6212
|
*/
|
|
6179
6213
|
async function getMilestoneBurndownEvents(projectId, milestoneId, options = {}) {
|
|
6180
6214
|
projectId = decodeURIComponent(projectId);
|
|
6181
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6215
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/burndown_events`);
|
|
6182
6216
|
appendFilterParams(url.searchParams, options);
|
|
6183
6217
|
const response = await fetch(url.toString(), {
|
|
6184
6218
|
...getFetchConfig(),
|
|
@@ -6192,7 +6226,7 @@ async function getMilestoneBurndownEvents(projectId, milestoneId, options = {})
|
|
|
6192
6226
|
*/
|
|
6193
6227
|
async function listGroupMilestones(groupId, options) {
|
|
6194
6228
|
groupId = decodeURIComponent(groupId);
|
|
6195
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${
|
|
6229
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/milestones`);
|
|
6196
6230
|
Object.entries(options).forEach(([key, value]) => {
|
|
6197
6231
|
// Drop blank entries inside arrays so they behave like the scalar blank guard below
|
|
6198
6232
|
const normalized = dropBlankArrayEntries(value);
|
|
@@ -6220,7 +6254,7 @@ async function listGroupMilestones(groupId, options) {
|
|
|
6220
6254
|
*/
|
|
6221
6255
|
async function getGroupMilestone(groupId, milestoneId) {
|
|
6222
6256
|
groupId = decodeURIComponent(groupId);
|
|
6223
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${
|
|
6257
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
|
|
6224
6258
|
const response = await fetch(url.toString(), {
|
|
6225
6259
|
...getFetchConfig(),
|
|
6226
6260
|
});
|
|
@@ -6233,7 +6267,7 @@ async function getGroupMilestone(groupId, milestoneId) {
|
|
|
6233
6267
|
*/
|
|
6234
6268
|
async function createGroupMilestone(groupId, options) {
|
|
6235
6269
|
groupId = decodeURIComponent(groupId);
|
|
6236
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${
|
|
6270
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/milestones`);
|
|
6237
6271
|
const response = await fetch(url.toString(), {
|
|
6238
6272
|
...getFetchConfig(),
|
|
6239
6273
|
method: "POST",
|
|
@@ -6248,7 +6282,7 @@ async function createGroupMilestone(groupId, options) {
|
|
|
6248
6282
|
*/
|
|
6249
6283
|
async function editGroupMilestone(groupId, milestoneId, options) {
|
|
6250
6284
|
groupId = decodeURIComponent(groupId);
|
|
6251
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${
|
|
6285
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
|
|
6252
6286
|
const response = await fetch(url.toString(), {
|
|
6253
6287
|
...getFetchConfig(),
|
|
6254
6288
|
method: "PUT",
|
|
@@ -6263,7 +6297,7 @@ async function editGroupMilestone(groupId, milestoneId, options) {
|
|
|
6263
6297
|
*/
|
|
6264
6298
|
async function deleteGroupMilestone(groupId, milestoneId) {
|
|
6265
6299
|
groupId = decodeURIComponent(groupId);
|
|
6266
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${
|
|
6300
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}`);
|
|
6267
6301
|
const response = await fetch(url.toString(), {
|
|
6268
6302
|
...getFetchConfig(),
|
|
6269
6303
|
method: "DELETE",
|
|
@@ -6275,7 +6309,7 @@ async function deleteGroupMilestone(groupId, milestoneId) {
|
|
|
6275
6309
|
*/
|
|
6276
6310
|
async function getGroupMilestoneIssues(groupId, milestoneId, options = {}) {
|
|
6277
6311
|
groupId = decodeURIComponent(groupId);
|
|
6278
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${
|
|
6312
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}/issues`);
|
|
6279
6313
|
appendFilterParams(url.searchParams, options);
|
|
6280
6314
|
const response = await fetch(url.toString(), {
|
|
6281
6315
|
...getFetchConfig(),
|
|
@@ -6289,7 +6323,7 @@ async function getGroupMilestoneIssues(groupId, milestoneId, options = {}) {
|
|
|
6289
6323
|
*/
|
|
6290
6324
|
async function getGroupMilestoneMergeRequests(groupId, milestoneId, options = {}) {
|
|
6291
6325
|
groupId = decodeURIComponent(groupId);
|
|
6292
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${
|
|
6326
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}/merge_requests`);
|
|
6293
6327
|
appendFilterParams(url.searchParams, options);
|
|
6294
6328
|
const response = await fetch(url.toString(), {
|
|
6295
6329
|
...getFetchConfig(),
|
|
@@ -6303,7 +6337,7 @@ async function getGroupMilestoneMergeRequests(groupId, milestoneId, options = {}
|
|
|
6303
6337
|
*/
|
|
6304
6338
|
async function getGroupMilestoneBurndownEvents(groupId, milestoneId, options = {}) {
|
|
6305
6339
|
groupId = decodeURIComponent(groupId);
|
|
6306
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${
|
|
6340
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}/burndown_events`);
|
|
6307
6341
|
appendFilterParams(url.searchParams, options);
|
|
6308
6342
|
const response = await fetch(url.toString(), {
|
|
6309
6343
|
...getFetchConfig(),
|
|
@@ -6374,7 +6408,7 @@ async function getUsers(usernames) {
|
|
|
6374
6408
|
*/
|
|
6375
6409
|
async function listCommits(projectId, options = {}) {
|
|
6376
6410
|
projectId = decodeURIComponent(projectId);
|
|
6377
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6411
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/commits`);
|
|
6378
6412
|
// Add query parameters
|
|
6379
6413
|
if (options.ref_name)
|
|
6380
6414
|
url.searchParams.append("ref_name", options.ref_name);
|
|
@@ -6418,7 +6452,7 @@ async function listCommits(projectId, options = {}) {
|
|
|
6418
6452
|
*/
|
|
6419
6453
|
async function getCommit(projectId, sha, stats) {
|
|
6420
6454
|
projectId = decodeURIComponent(projectId);
|
|
6421
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6455
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/commits/${encodeGitLabPathSegment(sha)}`);
|
|
6422
6456
|
if (stats) {
|
|
6423
6457
|
url.searchParams.append("stats", "true");
|
|
6424
6458
|
}
|
|
@@ -6440,7 +6474,7 @@ async function getCommit(projectId, sha, stats) {
|
|
|
6440
6474
|
*/
|
|
6441
6475
|
async function getCommitDiff(projectId, sha, full_diff) {
|
|
6442
6476
|
projectId = decodeURIComponent(projectId);
|
|
6443
|
-
const baseUrl = `${getEffectiveApiUrl()}/projects/${
|
|
6477
|
+
const baseUrl = `${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/commits/${encodeGitLabPathSegment(sha)}/diff`;
|
|
6444
6478
|
let allDiffs = [];
|
|
6445
6479
|
let page = 1;
|
|
6446
6480
|
while (true) {
|
|
@@ -6479,7 +6513,7 @@ async function getCommitDiff(projectId, sha, full_diff) {
|
|
|
6479
6513
|
*/
|
|
6480
6514
|
async function getFileBlame(projectId, options) {
|
|
6481
6515
|
projectId = decodeURIComponent(projectId);
|
|
6482
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6516
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/files/${encodeGitLabPathSegment(options.file_path)}/blame`);
|
|
6483
6517
|
url.searchParams.append("ref", options.ref);
|
|
6484
6518
|
if (options.range_start !== undefined && options.range_end !== undefined) {
|
|
6485
6519
|
url.searchParams.append("range[start]", options.range_start.toString());
|
|
@@ -6502,7 +6536,7 @@ async function getFileBlame(projectId, options) {
|
|
|
6502
6536
|
*/
|
|
6503
6537
|
async function listCommitStatuses(projectId, sha, options = {}) {
|
|
6504
6538
|
projectId = decodeURIComponent(projectId);
|
|
6505
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6539
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/commits/${encodeGitLabPathSegment(sha)}/statuses`);
|
|
6506
6540
|
appendFilterParams(url.searchParams, options);
|
|
6507
6541
|
const response = await fetch(url.toString(), {
|
|
6508
6542
|
...getFetchConfig(),
|
|
@@ -6524,7 +6558,7 @@ async function createCommitStatus(projectId, sha, options) {
|
|
|
6524
6558
|
throw new Error("Use either name or context when creating a commit status, not both.");
|
|
6525
6559
|
}
|
|
6526
6560
|
projectId = decodeURIComponent(projectId);
|
|
6527
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6561
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/statuses/${encodeGitLabPathSegment(sha)}`);
|
|
6528
6562
|
appendFilterParams(url.searchParams, options);
|
|
6529
6563
|
const response = await fetch(url.toString(), {
|
|
6530
6564
|
...getFetchConfig(),
|
|
@@ -6614,7 +6648,7 @@ async function listProjectMembers(projectId, options = {}) {
|
|
|
6614
6648
|
projectId = decodeURIComponent(projectId);
|
|
6615
6649
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
6616
6650
|
const membersPath = options.include_inheritance ? "members/all" : "members";
|
|
6617
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6651
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/${membersPath}`);
|
|
6618
6652
|
// Add query parameters
|
|
6619
6653
|
if (options.query)
|
|
6620
6654
|
url.searchParams.append("query", options.query);
|
|
@@ -6644,7 +6678,7 @@ async function listProjectMembers(projectId, options = {}) {
|
|
|
6644
6678
|
async function listGroupMembers(groupId, options = {}) {
|
|
6645
6679
|
groupId = decodeURIComponent(groupId);
|
|
6646
6680
|
const membersPath = options.include_inheritance ? "members/all" : "members";
|
|
6647
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${
|
|
6681
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/${membersPath}`);
|
|
6648
6682
|
if (options.query)
|
|
6649
6683
|
url.searchParams.append("query", options.query);
|
|
6650
6684
|
if (options.user_ids) {
|
|
@@ -6671,7 +6705,7 @@ async function listGroupMembers(groupId, options = {}) {
|
|
|
6671
6705
|
*/
|
|
6672
6706
|
async function listGroupIterations(groupId, options = {}) {
|
|
6673
6707
|
groupId = decodeURIComponent(groupId);
|
|
6674
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${
|
|
6708
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeGitLabPathSegment(groupId)}/iterations`);
|
|
6675
6709
|
// クエリパラメータの追加
|
|
6676
6710
|
if (options.state)
|
|
6677
6711
|
url.searchParams.append("state", options.state);
|
|
@@ -6701,7 +6735,7 @@ async function listGroupIterations(groupId, options = {}) {
|
|
|
6701
6735
|
// --- CI/CD Variables ---
|
|
6702
6736
|
async function listProjectVariables(projectId, options = {}) {
|
|
6703
6737
|
projectId = decodeURIComponent(projectId);
|
|
6704
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6738
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/variables`);
|
|
6705
6739
|
Object.entries(options).forEach(([key, value]) => {
|
|
6706
6740
|
if (key === "filter" && typeof value === "object" && value !== null) {
|
|
6707
6741
|
Object.entries(value).forEach(([filterKey, filterValue]) => {
|
|
@@ -6718,7 +6752,7 @@ async function listProjectVariables(projectId, options = {}) {
|
|
|
6718
6752
|
}
|
|
6719
6753
|
async function getProjectVariable(projectId, key, filter) {
|
|
6720
6754
|
projectId = decodeURIComponent(projectId);
|
|
6721
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6755
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/variables/${encodeGitLabPathSegment(key)}`);
|
|
6722
6756
|
if (filter?.environment_scope) {
|
|
6723
6757
|
url.searchParams.append("filter[environment_scope]", filter.environment_scope);
|
|
6724
6758
|
}
|
|
@@ -6729,7 +6763,7 @@ async function getProjectVariable(projectId, key, filter) {
|
|
|
6729
6763
|
}
|
|
6730
6764
|
async function createProjectVariable(projectId, options) {
|
|
6731
6765
|
projectId = decodeURIComponent(projectId);
|
|
6732
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
6766
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/variables`, { ...getFetchConfig(), method: "POST", body: JSON.stringify(options) });
|
|
6733
6767
|
await handleGitLabError(response);
|
|
6734
6768
|
const data = await response.json();
|
|
6735
6769
|
return GitLabCiVariableSchema.parse(data);
|
|
@@ -6737,7 +6771,7 @@ async function createProjectVariable(projectId, options) {
|
|
|
6737
6771
|
async function updateProjectVariable(projectId, key, options) {
|
|
6738
6772
|
projectId = decodeURIComponent(projectId);
|
|
6739
6773
|
const { filter, ...body } = options;
|
|
6740
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6774
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/variables/${encodeGitLabPathSegment(key)}`);
|
|
6741
6775
|
if (filter?.environment_scope) {
|
|
6742
6776
|
url.searchParams.append("filter[environment_scope]", filter.environment_scope);
|
|
6743
6777
|
}
|
|
@@ -6752,7 +6786,7 @@ async function updateProjectVariable(projectId, key, options) {
|
|
|
6752
6786
|
}
|
|
6753
6787
|
async function deleteProjectVariable(projectId, key, filter) {
|
|
6754
6788
|
projectId = decodeURIComponent(projectId);
|
|
6755
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6789
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/variables/${encodeGitLabPathSegment(key)}`);
|
|
6756
6790
|
if (filter?.environment_scope) {
|
|
6757
6791
|
url.searchParams.append("filter[environment_scope]", filter.environment_scope);
|
|
6758
6792
|
}
|
|
@@ -6760,7 +6794,7 @@ async function deleteProjectVariable(projectId, key, filter) {
|
|
|
6760
6794
|
await handleGitLabError(response);
|
|
6761
6795
|
}
|
|
6762
6796
|
async function listGroupVariables(groupId, options = {}) {
|
|
6763
|
-
const encoded =
|
|
6797
|
+
const encoded = encodeGitLabPathSegment(groupId);
|
|
6764
6798
|
const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables`);
|
|
6765
6799
|
Object.entries(options).forEach(([key, value]) => {
|
|
6766
6800
|
if (key === "filter" && typeof value === "object" && value !== null) {
|
|
@@ -6777,8 +6811,8 @@ async function listGroupVariables(groupId, options = {}) {
|
|
|
6777
6811
|
return z.array(GitLabCiVariableSchema).parse(data);
|
|
6778
6812
|
}
|
|
6779
6813
|
async function getGroupVariable(groupId, key, filter) {
|
|
6780
|
-
const encoded =
|
|
6781
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${
|
|
6814
|
+
const encoded = encodeGitLabPathSegment(groupId);
|
|
6815
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${encodeGitLabPathSegment(key)}`);
|
|
6782
6816
|
if (filter?.environment_scope) {
|
|
6783
6817
|
url.searchParams.append("filter[environment_scope]", filter.environment_scope);
|
|
6784
6818
|
}
|
|
@@ -6788,7 +6822,7 @@ async function getGroupVariable(groupId, key, filter) {
|
|
|
6788
6822
|
return GitLabCiVariableSchema.parse(data);
|
|
6789
6823
|
}
|
|
6790
6824
|
async function createGroupVariable(groupId, options) {
|
|
6791
|
-
const encoded =
|
|
6825
|
+
const encoded = encodeGitLabPathSegment(groupId);
|
|
6792
6826
|
const response = await fetch(`${getEffectiveApiUrl()}/groups/${encoded}/variables`, {
|
|
6793
6827
|
...getFetchConfig(),
|
|
6794
6828
|
method: "POST",
|
|
@@ -6799,9 +6833,9 @@ async function createGroupVariable(groupId, options) {
|
|
|
6799
6833
|
return GitLabCiVariableSchema.parse(data);
|
|
6800
6834
|
}
|
|
6801
6835
|
async function updateGroupVariable(groupId, key, options) {
|
|
6802
|
-
const encoded =
|
|
6836
|
+
const encoded = encodeGitLabPathSegment(groupId);
|
|
6803
6837
|
const { filter, ...body } = options;
|
|
6804
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${
|
|
6838
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${encodeGitLabPathSegment(key)}`);
|
|
6805
6839
|
if (filter?.environment_scope) {
|
|
6806
6840
|
url.searchParams.append("filter[environment_scope]", filter.environment_scope);
|
|
6807
6841
|
}
|
|
@@ -6815,8 +6849,8 @@ async function updateGroupVariable(groupId, key, options) {
|
|
|
6815
6849
|
return GitLabCiVariableSchema.parse(data);
|
|
6816
6850
|
}
|
|
6817
6851
|
async function deleteGroupVariable(groupId, key, filter) {
|
|
6818
|
-
const encoded =
|
|
6819
|
-
const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${
|
|
6852
|
+
const encoded = encodeGitLabPathSegment(groupId);
|
|
6853
|
+
const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${encodeGitLabPathSegment(key)}`);
|
|
6820
6854
|
if (filter?.environment_scope) {
|
|
6821
6855
|
url.searchParams.append("filter[environment_scope]", filter.environment_scope);
|
|
6822
6856
|
}
|
|
@@ -6902,7 +6936,7 @@ async function listDependencyProxyBlobs(groupPath, options = {}) {
|
|
|
6902
6936
|
};
|
|
6903
6937
|
}
|
|
6904
6938
|
async function purgeDependencyProxyCache(groupId) {
|
|
6905
|
-
const encoded =
|
|
6939
|
+
const encoded = encodeGitLabPathSegment(groupId);
|
|
6906
6940
|
const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/dependency_proxy/cache`);
|
|
6907
6941
|
const response = await fetch(url.toString(), { ...getFetchConfig(), method: "DELETE" });
|
|
6908
6942
|
await handleGitLabError(response);
|
|
@@ -6932,7 +6966,7 @@ async function resolveProjectFullPath(projectId) {
|
|
|
6932
6966
|
if (!/^\d+$/.test(decoded)) {
|
|
6933
6967
|
return decoded;
|
|
6934
6968
|
}
|
|
6935
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
6969
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(decoded)}`);
|
|
6936
6970
|
const response = await fetch(url.toString(), { ...getFetchConfig() });
|
|
6937
6971
|
await handleGitLabError(response);
|
|
6938
6972
|
const project = (await response.json());
|
|
@@ -7243,7 +7277,7 @@ async function markdownUpload(projectId, filePath, content, filename) {
|
|
|
7243
7277
|
// Create form data
|
|
7244
7278
|
const form = new FormData();
|
|
7245
7279
|
form.append("file", new File([fileBuffer], fileName, { type: "application/octet-stream" }));
|
|
7246
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
7280
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/uploads`);
|
|
7247
7281
|
const defaultFetchConfig = getFetchConfig();
|
|
7248
7282
|
delete defaultFetchConfig.headers["Content-Type"];
|
|
7249
7283
|
const response = await fetch(url.toString(), {
|
|
@@ -7284,11 +7318,8 @@ function getSafeAttachmentFilename(filename) {
|
|
|
7284
7318
|
async function downloadAttachment(projectId, secret, filename, localPath) {
|
|
7285
7319
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7286
7320
|
const safeFilename = getSafeAttachmentFilename(filename);
|
|
7287
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
7288
|
-
const response = await
|
|
7289
|
-
...getFetchConfig(),
|
|
7290
|
-
method: "GET",
|
|
7291
|
-
});
|
|
7321
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/uploads/${encodeGitLabPathSegment(secret)}/${encodeGitLabPathSegment(safeFilename)}`);
|
|
7322
|
+
const response = await fetchWithValidatedRedirects(url.toString(), downloadRedirectOptions());
|
|
7292
7323
|
if (!response.ok) {
|
|
7293
7324
|
await handleGitLabError(response);
|
|
7294
7325
|
}
|
|
@@ -7332,7 +7363,7 @@ async function listEvents(options = {}) {
|
|
|
7332
7363
|
*/
|
|
7333
7364
|
async function getProjectEvents(projectId, options = {}) {
|
|
7334
7365
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7335
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
7366
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/events`);
|
|
7336
7367
|
// Add all query parameters
|
|
7337
7368
|
appendFilterParams(url.searchParams, options);
|
|
7338
7369
|
const response = await fetch(url.toString(), { ...getFetchConfig() });
|
|
@@ -7351,7 +7382,7 @@ async function getProjectEvents(projectId, options = {}) {
|
|
|
7351
7382
|
*/
|
|
7352
7383
|
async function listReleases(projectId, options = {}) {
|
|
7353
7384
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7354
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
7385
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/releases`);
|
|
7355
7386
|
// Add query parameters
|
|
7356
7387
|
appendFilterParams(url.searchParams, options);
|
|
7357
7388
|
const response = await fetch(url.toString(), {
|
|
@@ -7371,7 +7402,7 @@ async function listReleases(projectId, options = {}) {
|
|
|
7371
7402
|
*/
|
|
7372
7403
|
async function getRelease(projectId, tagName, includeHtmlDescription) {
|
|
7373
7404
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7374
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
7405
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/releases/${encodeGitLabPathSegment(tagName)}`);
|
|
7375
7406
|
if (includeHtmlDescription !== undefined) {
|
|
7376
7407
|
url.searchParams.append("include_html_description", includeHtmlDescription.toString());
|
|
7377
7408
|
}
|
|
@@ -7391,7 +7422,7 @@ async function getRelease(projectId, tagName, includeHtmlDescription) {
|
|
|
7391
7422
|
*/
|
|
7392
7423
|
async function createRelease(projectId, options) {
|
|
7393
7424
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7394
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
7425
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/releases`, {
|
|
7395
7426
|
...getFetchConfig(),
|
|
7396
7427
|
method: "POST",
|
|
7397
7428
|
body: JSON.stringify(options),
|
|
@@ -7410,7 +7441,7 @@ async function createRelease(projectId, options) {
|
|
|
7410
7441
|
*/
|
|
7411
7442
|
async function updateRelease(projectId, tagName, options) {
|
|
7412
7443
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7413
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
7444
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/releases/${encodeGitLabPathSegment(tagName)}`, {
|
|
7414
7445
|
...getFetchConfig(),
|
|
7415
7446
|
method: "PUT",
|
|
7416
7447
|
body: JSON.stringify(options),
|
|
@@ -7428,7 +7459,7 @@ async function updateRelease(projectId, tagName, options) {
|
|
|
7428
7459
|
*/
|
|
7429
7460
|
async function deleteRelease(projectId, tagName) {
|
|
7430
7461
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7431
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
7462
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/releases/${encodeGitLabPathSegment(tagName)}`, {
|
|
7432
7463
|
...getFetchConfig(),
|
|
7433
7464
|
method: "DELETE",
|
|
7434
7465
|
});
|
|
@@ -7444,7 +7475,7 @@ async function deleteRelease(projectId, tagName) {
|
|
|
7444
7475
|
*/
|
|
7445
7476
|
async function createReleaseEvidence(projectId, tagName) {
|
|
7446
7477
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7447
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
7478
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/releases/${encodeGitLabPathSegment(tagName)}/evidence`, {
|
|
7448
7479
|
...getFetchConfig(),
|
|
7449
7480
|
method: "POST",
|
|
7450
7481
|
});
|
|
@@ -7460,9 +7491,7 @@ async function createReleaseEvidence(projectId, tagName) {
|
|
|
7460
7491
|
*/
|
|
7461
7492
|
async function downloadReleaseAsset(projectId, tagName, directAssetPath) {
|
|
7462
7493
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7463
|
-
const response = await
|
|
7464
|
-
...getFetchConfig(),
|
|
7465
|
-
});
|
|
7494
|
+
const response = await fetchWithValidatedRedirects(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/releases/${encodeGitLabPathSegment(tagName)}/downloads/${encodeGitLabPath(directAssetPath)}`, downloadRedirectOptions());
|
|
7466
7495
|
await handleGitLabError(response);
|
|
7467
7496
|
return await response.text();
|
|
7468
7497
|
}
|
|
@@ -7475,7 +7504,7 @@ async function downloadReleaseAsset(projectId, tagName, directAssetPath) {
|
|
|
7475
7504
|
*/
|
|
7476
7505
|
async function listTags(projectId, options = {}) {
|
|
7477
7506
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7478
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
7507
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/repository/tags`);
|
|
7479
7508
|
appendFilterParams(url.searchParams, options);
|
|
7480
7509
|
const response = await fetch(url.toString(), {
|
|
7481
7510
|
...getFetchConfig(),
|
|
@@ -7493,7 +7522,7 @@ async function listTags(projectId, options = {}) {
|
|
|
7493
7522
|
*/
|
|
7494
7523
|
async function getTag(projectId, tagName) {
|
|
7495
7524
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7496
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
7525
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/repository/tags/${encodeGitLabPathSegment(tagName)}`, {
|
|
7497
7526
|
...getFetchConfig(),
|
|
7498
7527
|
});
|
|
7499
7528
|
await handleGitLabError(response);
|
|
@@ -7509,7 +7538,7 @@ async function getTag(projectId, tagName) {
|
|
|
7509
7538
|
*/
|
|
7510
7539
|
async function createTag(projectId, options) {
|
|
7511
7540
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7512
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
7541
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/repository/tags`, {
|
|
7513
7542
|
...getFetchConfig(),
|
|
7514
7543
|
method: "POST",
|
|
7515
7544
|
body: JSON.stringify(options),
|
|
@@ -7526,7 +7555,7 @@ async function createTag(projectId, options) {
|
|
|
7526
7555
|
*/
|
|
7527
7556
|
async function deleteTag(projectId, tagName) {
|
|
7528
7557
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7529
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
7558
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/repository/tags/${encodeGitLabPathSegment(tagName)}`, {
|
|
7530
7559
|
...getFetchConfig(),
|
|
7531
7560
|
method: "DELETE",
|
|
7532
7561
|
});
|
|
@@ -7541,7 +7570,7 @@ async function deleteTag(projectId, tagName) {
|
|
|
7541
7570
|
*/
|
|
7542
7571
|
async function getTagSignature(projectId, tagName) {
|
|
7543
7572
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7544
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
7573
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/repository/tags/${encodeGitLabPathSegment(tagName)}/signature`, {
|
|
7545
7574
|
...getFetchConfig(),
|
|
7546
7575
|
});
|
|
7547
7576
|
await handleGitLabError(response);
|
|
@@ -7605,7 +7634,7 @@ async function handleToolCall(params) {
|
|
|
7605
7634
|
if (!isToolAllowedByPermissionMode(params.name)) {
|
|
7606
7635
|
throw new Error(GITLAB_PERMISSION_MODE === "readonly"
|
|
7607
7636
|
? `${params.name} is not allowed in read-only mode`
|
|
7608
|
-
: `${params.name} is not allowed in modify mode (delete operations are disabled)`);
|
|
7637
|
+
: `${params.name} is not allowed in modify mode (delete and teardown operations are disabled)`);
|
|
7609
7638
|
}
|
|
7610
7639
|
logger.info({ tool: params.name, event: "tool_call_start" }, `tool_call_start: ${params.name}`);
|
|
7611
7640
|
switch (params.name) {
|
|
@@ -7618,7 +7647,7 @@ async function handleToolCall(params) {
|
|
|
7618
7647
|
}
|
|
7619
7648
|
if (GITLAB_PERMISSION_MODE === "modify" &&
|
|
7620
7649
|
graphqlQueryContainsDeleteOperation(args.query)) {
|
|
7621
|
-
throw new Error("execute_graphql does not allow
|
|
7650
|
+
throw new Error("execute_graphql does not allow destructive mutations in modify mode");
|
|
7622
7651
|
}
|
|
7623
7652
|
const apiUrl = new URL(getEffectiveApiUrl());
|
|
7624
7653
|
// Build GraphQL endpoint preserving any instance subpath (e.g. /gitlab)
|
|
@@ -8208,7 +8237,7 @@ async function handleToolCall(params) {
|
|
|
8208
8237
|
}
|
|
8209
8238
|
case "get_namespace": {
|
|
8210
8239
|
const args = GetNamespaceSchema.parse(params.arguments);
|
|
8211
|
-
const url = new URL(`${GITLAB_API_URL}/namespaces/${
|
|
8240
|
+
const url = new URL(`${GITLAB_API_URL}/namespaces/${encodeGitLabPathSegment(args.namespace_id)}`);
|
|
8212
8241
|
const response = await fetch(url.toString(), {
|
|
8213
8242
|
...getFetchConfig(),
|
|
8214
8243
|
});
|
|
@@ -8221,7 +8250,7 @@ async function handleToolCall(params) {
|
|
|
8221
8250
|
}
|
|
8222
8251
|
case "verify_namespace": {
|
|
8223
8252
|
const args = VerifyNamespaceSchema.parse(params.arguments);
|
|
8224
|
-
const url = new URL(`${GITLAB_API_URL}/namespaces/${
|
|
8253
|
+
const url = new URL(`${GITLAB_API_URL}/namespaces/${encodeGitLabPathSegment(args.path)}/exists`);
|
|
8225
8254
|
if (args.parent_id !== undefined)
|
|
8226
8255
|
url.searchParams.set("parent_id", String(args.parent_id));
|
|
8227
8256
|
const response = await fetch(url.toString(), {
|
|
@@ -8238,7 +8267,7 @@ async function handleToolCall(params) {
|
|
|
8238
8267
|
const args = GetProjectSchema.parse(params.arguments);
|
|
8239
8268
|
const options = params.arguments;
|
|
8240
8269
|
const effectiveProjectId = getEffectiveProjectId(args.project_id);
|
|
8241
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
8270
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}`);
|
|
8242
8271
|
if (options.license)
|
|
8243
8272
|
url.searchParams.append("license", "true");
|
|
8244
8273
|
if (options.statistics)
|
|
@@ -8267,7 +8296,7 @@ async function handleToolCall(params) {
|
|
|
8267
8296
|
const { project_id, ...updates } = UpdateProjectSchema.parse(params.arguments);
|
|
8268
8297
|
const effectiveProjectId = getEffectiveProjectId(project_id);
|
|
8269
8298
|
const body = Object.fromEntries(Object.entries(updates).filter(([, value]) => value !== undefined));
|
|
8270
|
-
const response = await fetch(`${getEffectiveApiUrl()}/projects/${
|
|
8299
|
+
const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}`, {
|
|
8271
8300
|
...getFetchConfig(),
|
|
8272
8301
|
method: "PUT",
|
|
8273
8302
|
body: JSON.stringify(body),
|
|
@@ -8306,7 +8335,7 @@ async function handleToolCall(params) {
|
|
|
8306
8335
|
}
|
|
8307
8336
|
case "get_user": {
|
|
8308
8337
|
const args = GetUserSchema.parse(params.arguments);
|
|
8309
|
-
const url = new URL(`${getEffectiveApiUrl()}/users/${
|
|
8338
|
+
const url = new URL(`${getEffectiveApiUrl()}/users/${encodeGitLabPathSegment(args.user_id)}`);
|
|
8310
8339
|
const response = await fetch(url.toString(), {
|
|
8311
8340
|
...getFetchConfig(),
|
|
8312
8341
|
});
|
|
@@ -10186,7 +10215,7 @@ async function handleToolCall(params) {
|
|
|
10186
10215
|
case "get_branch": {
|
|
10187
10216
|
const args = GetBranchSchema.parse(params.arguments);
|
|
10188
10217
|
const projectId = decodeURIComponent(args.project_id);
|
|
10189
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
10218
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/branches/${encodeGitLabPathSegment(args.branch_name)}`);
|
|
10190
10219
|
const response = await fetch(url.toString(), {
|
|
10191
10220
|
...getFetchConfig(),
|
|
10192
10221
|
});
|
|
@@ -10200,7 +10229,7 @@ async function handleToolCall(params) {
|
|
|
10200
10229
|
case "list_branches": {
|
|
10201
10230
|
const args = ListBranchesSchema.parse(params.arguments);
|
|
10202
10231
|
const projectId = decodeURIComponent(args.project_id);
|
|
10203
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
10232
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/branches`);
|
|
10204
10233
|
if (args.search) {
|
|
10205
10234
|
url.searchParams.append("search", args.search);
|
|
10206
10235
|
}
|
|
@@ -10223,7 +10252,7 @@ async function handleToolCall(params) {
|
|
|
10223
10252
|
case "delete_branch": {
|
|
10224
10253
|
const args = DeleteBranchSchema.parse(params.arguments);
|
|
10225
10254
|
const projectId = decodeURIComponent(args.project_id);
|
|
10226
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
10255
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/branches/${encodeGitLabPathSegment(args.branch_name)}`);
|
|
10227
10256
|
const response = await fetch(url.toString(), {
|
|
10228
10257
|
...getFetchConfig(),
|
|
10229
10258
|
method: "DELETE",
|
|
@@ -10237,7 +10266,7 @@ async function handleToolCall(params) {
|
|
|
10237
10266
|
const args = ListProtectedBranchesSchema.parse(params.arguments);
|
|
10238
10267
|
const projectId = decodeURIComponent(args.project_id);
|
|
10239
10268
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
10240
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
10269
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/protected_branches`);
|
|
10241
10270
|
if (args.search)
|
|
10242
10271
|
url.searchParams.append("search", args.search);
|
|
10243
10272
|
if (args.page)
|
|
@@ -10257,7 +10286,7 @@ async function handleToolCall(params) {
|
|
|
10257
10286
|
const args = GetProtectedBranchSchema.parse(params.arguments);
|
|
10258
10287
|
const projectId = decodeURIComponent(args.project_id);
|
|
10259
10288
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
10260
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
10289
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/protected_branches/${encodeGitLabPathSegment(args.branch_name)}`);
|
|
10261
10290
|
const response = await fetch(url.toString(), {
|
|
10262
10291
|
...getFetchConfig(),
|
|
10263
10292
|
});
|
|
@@ -10271,7 +10300,7 @@ async function handleToolCall(params) {
|
|
|
10271
10300
|
const args = ProtectBranchSchema.parse(params.arguments);
|
|
10272
10301
|
const projectId = decodeURIComponent(args.project_id);
|
|
10273
10302
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
10274
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
10303
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/protected_branches`);
|
|
10275
10304
|
const body = { name: args.branch_name };
|
|
10276
10305
|
if (args.push_access_level !== undefined)
|
|
10277
10306
|
body.push_access_level = args.push_access_level;
|
|
@@ -10298,7 +10327,7 @@ async function handleToolCall(params) {
|
|
|
10298
10327
|
const args = UnprotectBranchSchema.parse(params.arguments);
|
|
10299
10328
|
const projectId = decodeURIComponent(args.project_id);
|
|
10300
10329
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
10301
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
10330
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}/protected_branches/${encodeGitLabPathSegment(args.branch_name)}`);
|
|
10302
10331
|
const response = await fetch(url.toString(), {
|
|
10303
10332
|
...getFetchConfig(),
|
|
10304
10333
|
method: "DELETE",
|
|
@@ -10312,7 +10341,7 @@ async function handleToolCall(params) {
|
|
|
10312
10341
|
const args = UpdateDefaultBranchSchema.parse(params.arguments);
|
|
10313
10342
|
const projectId = decodeURIComponent(args.project_id);
|
|
10314
10343
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
10315
|
-
const url = new URL(`${getEffectiveApiUrl()}/projects/${
|
|
10344
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(effectiveProjectId)}`);
|
|
10316
10345
|
const response = await fetch(url.toString(), {
|
|
10317
10346
|
...getFetchConfig(),
|
|
10318
10347
|
method: "PUT",
|
|
@@ -10369,6 +10398,7 @@ function buildDownloadProxyDeps() {
|
|
|
10369
10398
|
getDispatcherForUrl: clientPool.getDispatcherForUrl.bind(clientPool),
|
|
10370
10399
|
fetch: undiciFetch,
|
|
10371
10400
|
logger,
|
|
10401
|
+
isTrustedRedirectHost: isTrustedGitLabRedirectHost,
|
|
10372
10402
|
};
|
|
10373
10403
|
}
|
|
10374
10404
|
/**
|
|
@@ -10430,9 +10460,68 @@ async function startSSEServer() {
|
|
|
10430
10460
|
}
|
|
10431
10461
|
next();
|
|
10432
10462
|
};
|
|
10433
|
-
|
|
10463
|
+
// Session IDs are untrusted map keys (POST /messages?sessionId=). A null
|
|
10464
|
+
// prototype prevents inherited names such as "constructor" from masquerading
|
|
10465
|
+
// as live transports, matching the Streamable HTTP transport.
|
|
10466
|
+
const transports = Object.create(null);
|
|
10467
|
+
const sessionLastActivity = new Map();
|
|
10434
10468
|
let shuttingDown = false;
|
|
10435
|
-
|
|
10469
|
+
// Mirror the Streamable HTTP controls so the SSE transport cannot be used to
|
|
10470
|
+
// exhaust the process: a session cap, a per-IP creation rate limit and an idle
|
|
10471
|
+
// timeout that closes connections which never send anything.
|
|
10472
|
+
const maxSessions = Number.parseInt(process.env.MAX_SESSIONS || "1000", 10);
|
|
10473
|
+
const maxSseConnectionsPerMinute = Number.parseInt(process.env.MAX_REQUESTS_PER_MINUTE || "60", 10);
|
|
10474
|
+
const idleTimeoutMs = Math.max(SESSION_TIMEOUT_SECONDS, 1) * 1000;
|
|
10475
|
+
const sseConnectionRateLimit = rateLimit({
|
|
10476
|
+
windowMs: 60_000,
|
|
10477
|
+
limit: maxSseConnectionsPerMinute,
|
|
10478
|
+
keyGenerator: (req) => ipKeyGenerator(normalizeProxyClientIpForRateLimit(req.ip ?? "")),
|
|
10479
|
+
standardHeaders: true,
|
|
10480
|
+
legacyHeaders: false,
|
|
10481
|
+
handler: (_req, res) => {
|
|
10482
|
+
res.status(429).json({
|
|
10483
|
+
error: "Rate limit exceeded",
|
|
10484
|
+
message: `Maximum ${maxSseConnectionsPerMinute} SSE connections per minute allowed`,
|
|
10485
|
+
});
|
|
10486
|
+
},
|
|
10487
|
+
});
|
|
10488
|
+
/**
|
|
10489
|
+
* Reclaim SSE sessions that have not received a POST /messages request within
|
|
10490
|
+
* the idle timeout.
|
|
10491
|
+
*
|
|
10492
|
+
* On /sse it is correct to idle-close an open GET: the stream alone is not
|
|
10493
|
+
* proof of a live client, so a client that connects and never sends anything
|
|
10494
|
+
* would otherwise hold a MAX_SESSIONS slot forever. This intentionally
|
|
10495
|
+
* differs from the Streamable HTTP GET /mcp handler, which pauses inactivity
|
|
10496
|
+
* expiry while its stream is open so list_changed can still be pushed - do
|
|
10497
|
+
* not copy that rule here.
|
|
10498
|
+
*/
|
|
10499
|
+
const closeIdleSseSessions = () => {
|
|
10500
|
+
const now = Date.now();
|
|
10501
|
+
for (const [sessionId, lastActivity] of sessionLastActivity) {
|
|
10502
|
+
if (now - lastActivity < idleTimeoutMs)
|
|
10503
|
+
continue;
|
|
10504
|
+
const transport = transports[sessionId];
|
|
10505
|
+
delete transports[sessionId];
|
|
10506
|
+
sessionLastActivity.delete(sessionId);
|
|
10507
|
+
logger.info({ sessionId, event: "sse_session_idle_closed" }, `SSE session ${sessionId} closed after ${SESSION_TIMEOUT_SECONDS}s of inactivity`);
|
|
10508
|
+
if (transport) {
|
|
10509
|
+
void transport.close().catch(error => {
|
|
10510
|
+
logger.error({ err: error }, "Error closing idle SSE transport");
|
|
10511
|
+
});
|
|
10512
|
+
}
|
|
10513
|
+
}
|
|
10514
|
+
};
|
|
10515
|
+
const idleSweep = setInterval(closeIdleSseSessions, Math.min(idleTimeoutMs, 60_000));
|
|
10516
|
+
idleSweep.unref();
|
|
10517
|
+
app.get("/sse", rejectDnsRebinding, requireSseAuth, sseConnectionRateLimit, async (req, res) => {
|
|
10518
|
+
if (Object.keys(transports).length >= maxSessions) {
|
|
10519
|
+
res.status(503).json({
|
|
10520
|
+
error: "Server capacity reached",
|
|
10521
|
+
message: `Maximum ${maxSessions} concurrent sessions allowed. Please try again later.`,
|
|
10522
|
+
});
|
|
10523
|
+
return;
|
|
10524
|
+
}
|
|
10436
10525
|
const serverInstance = createServer();
|
|
10437
10526
|
const effectiveHosts = getEffectiveAllowedHosts(req);
|
|
10438
10527
|
const transport = new SSEServerTransport("/messages", res, {
|
|
@@ -10440,16 +10529,38 @@ async function startSSEServer() {
|
|
|
10440
10529
|
allowedHosts: [...effectiveHosts],
|
|
10441
10530
|
allowedOrigins: [...effectiveHosts].flatMap(host => [`http://${host}`, `https://${host}`]),
|
|
10442
10531
|
});
|
|
10443
|
-
|
|
10532
|
+
const sessionId = transport.sessionId;
|
|
10533
|
+
transports[sessionId] = transport;
|
|
10534
|
+
sessionLastActivity.set(sessionId, Date.now());
|
|
10444
10535
|
res.on("close", () => {
|
|
10445
|
-
delete transports[
|
|
10536
|
+
delete transports[sessionId];
|
|
10537
|
+
sessionLastActivity.delete(sessionId);
|
|
10446
10538
|
});
|
|
10447
|
-
|
|
10539
|
+
try {
|
|
10540
|
+
await serverInstance.connect(transport);
|
|
10541
|
+
}
|
|
10542
|
+
catch (error) {
|
|
10543
|
+
logger.error({ err: error, sessionId }, "SSE connection error");
|
|
10544
|
+
// Release the capacity slot immediately instead of waiting for the
|
|
10545
|
+
// client to disconnect or the idle sweep to run.
|
|
10546
|
+
delete transports[sessionId];
|
|
10547
|
+
sessionLastActivity.delete(sessionId);
|
|
10548
|
+
if (!res.headersSent) {
|
|
10549
|
+
res.status(500).json({
|
|
10550
|
+
error: "Internal server error",
|
|
10551
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
10552
|
+
});
|
|
10553
|
+
}
|
|
10554
|
+
else {
|
|
10555
|
+
res.end();
|
|
10556
|
+
}
|
|
10557
|
+
}
|
|
10448
10558
|
});
|
|
10449
10559
|
app.post("/messages", rejectDnsRebinding, requireSseAuth, async (req, res) => {
|
|
10450
10560
|
const sessionId = req.query.sessionId;
|
|
10451
10561
|
const transport = transports[sessionId];
|
|
10452
10562
|
if (transport) {
|
|
10563
|
+
sessionLastActivity.set(sessionId, Date.now());
|
|
10453
10564
|
await transport.handlePostMessage(req, res);
|
|
10454
10565
|
}
|
|
10455
10566
|
else {
|
|
@@ -10458,10 +10569,18 @@ async function startSSEServer() {
|
|
|
10458
10569
|
});
|
|
10459
10570
|
registerDownloadProxy(app, buildDownloadProxyDeps());
|
|
10460
10571
|
app.get("/health", (_, res) => {
|
|
10461
|
-
|
|
10462
|
-
|
|
10572
|
+
const activeSessions = Object.keys(transports).length;
|
|
10573
|
+
const isHealthy = activeSessions < maxSessions;
|
|
10574
|
+
if (!isHealthy) {
|
|
10575
|
+
logger.warn({ activeSessions, maxSessions }, "Health check degraded: active SSE session capacity reached");
|
|
10576
|
+
}
|
|
10577
|
+
res.status(isHealthy ? 200 : 503).json({
|
|
10578
|
+
status: isHealthy ? "healthy" : "degraded",
|
|
10463
10579
|
version: SERVER_VERSION,
|
|
10464
10580
|
transport: TransportMode.SSE,
|
|
10581
|
+
activeSessions,
|
|
10582
|
+
maxSessions,
|
|
10583
|
+
uptime: process.uptime(),
|
|
10465
10584
|
});
|
|
10466
10585
|
});
|
|
10467
10586
|
const httpServer = app.listen(Number(PORT), HOST, () => {
|
|
@@ -10473,6 +10592,7 @@ async function startSSEServer() {
|
|
|
10473
10592
|
return;
|
|
10474
10593
|
shuttingDown = true;
|
|
10475
10594
|
logger.info(`${signal} received, shutting down SSE server...`);
|
|
10595
|
+
clearInterval(idleSweep);
|
|
10476
10596
|
httpServer.close(() => logger.info("SSE HTTP server closed"));
|
|
10477
10597
|
await Promise.allSettled(Object.values(transports).map(async (transport) => {
|
|
10478
10598
|
try {
|