History

feat(scm): Gitlab (#2462)

Julius Marminge committed 6 months agov0.0.22-nightly.20260503.186

Commit 0ce7e561 parent 350d76e24 files · +1606 −185Browse files

Showing the changes to apps/server/src/sourceControl/GitLabSourceControlProvider.ts (1 of 24 files). Show all

Unified · Split

apps/server/src/sourceControl/GitLabSourceControlProvider.ts+106−0
@@ -0,0 +1,106 @@
1import { Effect, Layer, Option } from "effect";
2import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts";
3
4import { GitLabCli, type GitLabCliError, type GitLabMergeRequestSummary } from "./GitLabCli.ts";
5import { SourceControlProvider, type SourceControlRefSelector } from "./SourceControlProvider.ts";
6
7function providerError(operation: string, cause: GitLabCliError): SourceControlProviderError {
8 return new SourceControlProviderError({
9 provider: "gitlab",
10 operation,
11 detail: cause.detail,
12 cause,
13 });
14}
15
16function toChangeRequest(summary: GitLabMergeRequestSummary): ChangeRequest {
17 return {
18 provider: "gitlab",
19 number: summary.number,
20 title: summary.title,
21 url: summary.url,
22 baseRefName: summary.baseRefName,
23 headRefName: summary.headRefName,
24 state: summary.state ?? "open",
25 updatedAt: summary.updatedAt ?? Option.none(),
26 ...(summary.isCrossRepository !== undefined
27 ? { isCrossRepository: summary.isCrossRepository }
28 : {}),
29 ...(summary.headRepositoryNameWithOwner !== undefined
30 ? { headRepositoryNameWithOwner: summary.headRepositoryNameWithOwner }
31 : {}),
32 ...(summary.headRepositoryOwnerLogin !== undefined
33 ? { headRepositoryOwnerLogin: summary.headRepositoryOwnerLogin }
34 : {}),
35 };
36}
37
38function sourceFromInput(input: {
39 readonly headSelector: string;
40 readonly source?: SourceControlRefSelector;
41}): SourceControlRefSelector | undefined {
42 if (input.source) {
43 return input.source;
44 }
45
46 const match = /^([^:/\s]+):(.+)$/u.exec(input.headSelector.trim());
47 const owner = match?.[1]?.trim();
48 const refName = match?.[2]?.trim();
49 return owner && refName ? { owner, refName } : undefined;
50}
51
52export const make = Effect.fn("makeGitLabSourceControlProvider")(function* () {
53 const gitlab = yield* GitLabCli;
54
55 return SourceControlProvider.of({
56 kind: "gitlab",
57 listChangeRequests: (input) => {
58 const source = sourceFromInput(input);
59 return gitlab
60 .listMergeRequests({
61 cwd: input.cwd,
62 headSelector: input.headSelector,
63 ...(source ? { source } : {}),
64 state: input.state,
65 ...(input.limit !== undefined ? { limit: input.limit } : {}),
66 })
67 .pipe(
68 Effect.map((items) => items.map(toChangeRequest)),
69 Effect.mapError((error) => providerError("listChangeRequests", error)),
70 );
71 },
72 getChangeRequest: (input) =>
73 gitlab.getMergeRequest(input).pipe(
74 Effect.map(toChangeRequest),
75 Effect.mapError((error) => providerError("getChangeRequest", error)),
76 ),
77 createChangeRequest: (input) => {
78 const source = sourceFromInput(input);
79 return gitlab
80 .createMergeRequest({
81 cwd: input.cwd,
82 baseBranch: input.baseRefName,
83 headSelector: input.headSelector,
84 ...(source ? { source } : {}),
85 ...(input.target ? { target: input.target } : {}),
86 title: input.title,
87 bodyFile: input.bodyFile,
88 })
89 .pipe(Effect.mapError((error) => providerError("createChangeRequest", error)));
90 },
91 getRepositoryCloneUrls: (input) =>
92 gitlab
93 .getRepositoryCloneUrls(input)
94 .pipe(Effect.mapError((error) => providerError("getRepositoryCloneUrls", error))),
95 getDefaultBranch: (input) =>
96 gitlab
97 .getDefaultBranch(input)
98 .pipe(Effect.mapError((error) => providerError("getDefaultBranch", error))),
99 checkoutChangeRequest: (input) =>
100 gitlab
101 .checkoutMergeRequest(input)
102 .pipe(Effect.mapError((error) => providerError("checkoutChangeRequest", error))),
103 });
104});
105
106export const layer = Layer.effect(SourceControlProvider, make());