Skip to content

Commit 30ea1cd

Browse files
committed
feat(gitlab): add repository, code-review, and CI job tools + validation fixes
Expand the GitLab integration with 12 new tools (all host-aware via getGitLabApiBase, wired through types/index/registry/block): - Repository: list_repository_tree, get_file, create_file, update_file, create_branch, list_branches, list_commits - Code review: get_merge_request_changes, approve_merge_request - CI jobs: list_pipeline_jobs, get_job_log, play_job Validation fixes from /validate-integration: - Correct the block inputs key (credential -> accessToken) so it matches the subBlock id and the params the block reads - Trim projectId before encoding in all tool request URLs (input hygiene) /validate-connector and /validate-trigger passed clean against the GitLab REST API v4 docs — no changes required.
1 parent 5a938e5 commit 30ea1cd

34 files changed

Lines changed: 1935 additions & 39 deletions

apps/sim/blocks/blocks/gitlab.ts

Lines changed: 344 additions & 3 deletions
Large diffs are not rendered by default.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import type {
2+
GitLabApproveMergeRequestParams,
3+
GitLabApproveMergeRequestResponse,
4+
} from '@/tools/gitlab/types'
5+
import { getGitLabApiBase } from '@/tools/gitlab/utils'
6+
import type { ToolConfig } from '@/tools/types'
7+
8+
export const gitlabApproveMergeRequestTool: ToolConfig<
9+
GitLabApproveMergeRequestParams,
10+
GitLabApproveMergeRequestResponse
11+
> = {
12+
id: 'gitlab_approve_merge_request',
13+
name: 'GitLab Approve Merge Request',
14+
description: 'Approve a GitLab merge request',
15+
version: '1.0.0',
16+
17+
params: {
18+
accessToken: {
19+
type: 'string',
20+
required: true,
21+
visibility: 'user-only',
22+
description: 'GitLab Personal Access Token',
23+
},
24+
host: {
25+
type: 'string',
26+
required: false,
27+
visibility: 'user-only',
28+
description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.',
29+
},
30+
projectId: {
31+
type: 'string',
32+
required: true,
33+
visibility: 'user-or-llm',
34+
description: 'Project ID or URL-encoded path',
35+
},
36+
mergeRequestIid: {
37+
type: 'number',
38+
required: true,
39+
visibility: 'user-or-llm',
40+
description: 'Merge request internal ID (IID)',
41+
},
42+
sha: {
43+
type: 'string',
44+
required: false,
45+
visibility: 'user-or-llm',
46+
description: 'HEAD SHA of the merge request to approve',
47+
},
48+
},
49+
50+
request: {
51+
url: (params) => {
52+
const encodedId = encodeURIComponent(String(params.projectId).trim())
53+
return `${getGitLabApiBase(params.host)}/projects/${encodedId}/merge_requests/${params.mergeRequestIid}/approve`
54+
},
55+
method: 'POST',
56+
headers: (params) => ({
57+
'Content-Type': 'application/json',
58+
'PRIVATE-TOKEN': params.accessToken,
59+
}),
60+
body: (params) => {
61+
const body: Record<string, unknown> = {}
62+
63+
if (params.sha) body.sha = params.sha
64+
65+
return body
66+
},
67+
},
68+
69+
transformResponse: async (response) => {
70+
if (!response.ok) {
71+
const errorText = await response.text()
72+
return {
73+
success: false,
74+
error: `GitLab API error: ${response.status} ${errorText}`,
75+
output: {},
76+
}
77+
}
78+
79+
const data = await response.json()
80+
81+
return {
82+
success: true,
83+
output: {
84+
approvalsRequired: data.approvals_required ?? null,
85+
approvalsLeft: data.approvals_left ?? null,
86+
approvedBy: data.approved_by ?? [],
87+
},
88+
}
89+
},
90+
91+
outputs: {
92+
approvalsRequired: {
93+
type: 'number',
94+
description: 'Number of approvals required',
95+
},
96+
approvalsLeft: {
97+
type: 'number',
98+
description: 'Number of approvals still needed',
99+
},
100+
approvedBy: {
101+
type: 'array',
102+
description: 'List of approvers',
103+
},
104+
},
105+
}

apps/sim/tools/gitlab/cancel_pipeline.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const gitlabCancelPipelineTool: ToolConfig<
4040

4141
request: {
4242
url: (params) => {
43-
const encodedId = encodeURIComponent(String(params.projectId))
43+
const encodedId = encodeURIComponent(String(params.projectId).trim())
4444
return `${getGitLabApiBase(params.host)}/projects/${encodedId}/pipelines/${params.pipelineId}/cancel`
4545
},
4646
method: 'POST',
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import type { GitLabCreateBranchParams, GitLabCreateBranchResponse } from '@/tools/gitlab/types'
2+
import { getGitLabApiBase } from '@/tools/gitlab/utils'
3+
import type { ToolConfig } from '@/tools/types'
4+
5+
export const gitlabCreateBranchTool: ToolConfig<
6+
GitLabCreateBranchParams,
7+
GitLabCreateBranchResponse
8+
> = {
9+
id: 'gitlab_create_branch',
10+
name: 'GitLab Create Branch',
11+
description: 'Create a new branch in a GitLab project repository',
12+
version: '1.0.0',
13+
14+
params: {
15+
accessToken: {
16+
type: 'string',
17+
required: true,
18+
visibility: 'user-only',
19+
description: 'GitLab Personal Access Token',
20+
},
21+
host: {
22+
type: 'string',
23+
required: false,
24+
visibility: 'user-only',
25+
description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.',
26+
},
27+
projectId: {
28+
type: 'string',
29+
required: true,
30+
visibility: 'user-or-llm',
31+
description: 'Project ID or URL-encoded path',
32+
},
33+
branch: {
34+
type: 'string',
35+
required: true,
36+
visibility: 'user-or-llm',
37+
description: 'Name of the new branch',
38+
},
39+
ref: {
40+
type: 'string',
41+
required: true,
42+
visibility: 'user-or-llm',
43+
description: 'Source branch/tag/SHA',
44+
},
45+
},
46+
47+
request: {
48+
url: (params) => {
49+
const encodedId = encodeURIComponent(String(params.projectId).trim())
50+
const queryParams = new URLSearchParams()
51+
queryParams.append('branch', String(params.branch))
52+
queryParams.append('ref', String(params.ref))
53+
return `${getGitLabApiBase(params.host)}/projects/${encodedId}/repository/branches?${queryParams.toString()}`
54+
},
55+
method: 'POST',
56+
headers: (params) => ({
57+
'PRIVATE-TOKEN': params.accessToken,
58+
}),
59+
},
60+
61+
transformResponse: async (response) => {
62+
if (!response.ok) {
63+
const errorText = await response.text()
64+
return {
65+
success: false,
66+
error: `GitLab API error: ${response.status} ${errorText}`,
67+
output: {},
68+
}
69+
}
70+
71+
const data = await response.json()
72+
73+
return {
74+
success: true,
75+
output: {
76+
name: data.name ?? null,
77+
webUrl: data.web_url ?? null,
78+
protected: data.protected ?? null,
79+
commit: data.commit ?? null,
80+
},
81+
}
82+
},
83+
84+
outputs: {
85+
name: {
86+
type: 'string',
87+
description: 'The created branch name',
88+
},
89+
webUrl: {
90+
type: 'string',
91+
description: 'The web URL of the branch',
92+
},
93+
protected: {
94+
type: 'boolean',
95+
description: 'Whether the branch is protected',
96+
},
97+
commit: {
98+
type: 'object',
99+
description: 'The commit the branch points to',
100+
},
101+
},
102+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import type { GitLabCreateFileParams, GitLabCreateFileResponse } from '@/tools/gitlab/types'
2+
import { getGitLabApiBase } from '@/tools/gitlab/utils'
3+
import type { ToolConfig } from '@/tools/types'
4+
5+
export const gitlabCreateFileTool: ToolConfig<GitLabCreateFileParams, GitLabCreateFileResponse> = {
6+
id: 'gitlab_create_file',
7+
name: 'GitLab Create File',
8+
description: 'Create a new file in a GitLab project repository',
9+
version: '1.0.0',
10+
11+
params: {
12+
accessToken: {
13+
type: 'string',
14+
required: true,
15+
visibility: 'user-only',
16+
description: 'GitLab Personal Access Token',
17+
},
18+
host: {
19+
type: 'string',
20+
required: false,
21+
visibility: 'user-only',
22+
description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.',
23+
},
24+
projectId: {
25+
type: 'string',
26+
required: true,
27+
visibility: 'user-or-llm',
28+
description: 'Project ID or URL-encoded path',
29+
},
30+
filePath: {
31+
type: 'string',
32+
required: true,
33+
visibility: 'user-or-llm',
34+
description: 'Path to the file in the repository',
35+
},
36+
branch: {
37+
type: 'string',
38+
required: true,
39+
visibility: 'user-or-llm',
40+
description: 'Branch to commit the new file to',
41+
},
42+
content: {
43+
type: 'string',
44+
required: true,
45+
visibility: 'user-or-llm',
46+
description: 'File content',
47+
},
48+
commitMessage: {
49+
type: 'string',
50+
required: true,
51+
visibility: 'user-or-llm',
52+
description: 'Commit message',
53+
},
54+
},
55+
56+
request: {
57+
url: (params) => {
58+
const encodedId = encodeURIComponent(String(params.projectId).trim())
59+
const encodedPath = encodeURIComponent(String(params.filePath))
60+
return `${getGitLabApiBase(params.host)}/projects/${encodedId}/repository/files/${encodedPath}`
61+
},
62+
method: 'POST',
63+
headers: (params) => ({
64+
'Content-Type': 'application/json',
65+
'PRIVATE-TOKEN': params.accessToken,
66+
}),
67+
body: (params) => ({
68+
branch: params.branch,
69+
content: params.content,
70+
commit_message: params.commitMessage,
71+
}),
72+
},
73+
74+
transformResponse: async (response) => {
75+
if (!response.ok) {
76+
const errorText = await response.text()
77+
return {
78+
success: false,
79+
error: `GitLab API error: ${response.status} ${errorText}`,
80+
output: {},
81+
}
82+
}
83+
84+
const data = await response.json()
85+
86+
return {
87+
success: true,
88+
output: {
89+
filePath: data.file_path ?? null,
90+
branch: data.branch ?? null,
91+
},
92+
}
93+
},
94+
95+
outputs: {
96+
filePath: {
97+
type: 'string',
98+
description: 'The created file path',
99+
},
100+
branch: {
101+
type: 'string',
102+
description: 'The branch the file was committed to',
103+
},
104+
},
105+
}

apps/sim/tools/gitlab/create_issue.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export const gitlabCreateIssueTool: ToolConfig<GitLabCreateIssueParams, GitLabCr
7474

7575
request: {
7676
url: (params) => {
77-
const encodedId = encodeURIComponent(String(params.projectId))
77+
const encodedId = encodeURIComponent(String(params.projectId).trim())
7878
return `${getGitLabApiBase(params.host)}/projects/${encodedId}/issues`
7979
},
8080
method: 'POST',

apps/sim/tools/gitlab/create_issue_note.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const gitlabCreateIssueNoteTool: ToolConfig<
4646

4747
request: {
4848
url: (params) => {
49-
const encodedId = encodeURIComponent(String(params.projectId))
49+
const encodedId = encodeURIComponent(String(params.projectId).trim())
5050
return `${getGitLabApiBase(params.host)}/projects/${encodedId}/issues/${params.issueIid}/notes`
5151
},
5252
method: 'POST',

apps/sim/tools/gitlab/create_merge_request.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export const gitlabCreateMergeRequestTool: ToolConfig<
9797

9898
request: {
9999
url: (params) => {
100-
const encodedId = encodeURIComponent(String(params.projectId))
100+
const encodedId = encodeURIComponent(String(params.projectId).trim())
101101
return `${getGitLabApiBase(params.host)}/projects/${encodedId}/merge_requests`
102102
},
103103
method: 'POST',

apps/sim/tools/gitlab/create_merge_request_note.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const gitlabCreateMergeRequestNoteTool: ToolConfig<
4949

5050
request: {
5151
url: (params) => {
52-
const encodedId = encodeURIComponent(String(params.projectId))
52+
const encodedId = encodeURIComponent(String(params.projectId).trim())
5353
return `${getGitLabApiBase(params.host)}/projects/${encodedId}/merge_requests/${params.mergeRequestIid}/notes`
5454
},
5555
method: 'POST',

apps/sim/tools/gitlab/create_pipeline.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export const gitlabCreatePipelineTool: ToolConfig<
4747

4848
request: {
4949
url: (params) => {
50-
const encodedId = encodeURIComponent(String(params.projectId))
50+
const encodedId = encodeURIComponent(String(params.projectId).trim())
5151
return `${getGitLabApiBase(params.host)}/projects/${encodedId}/pipeline`
5252
},
5353
method: 'POST',

0 commit comments

Comments
 (0)