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 @@
1+import { Effect, Layer, Option } from "effect";
2+import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts";
3+
4+import { GitLabCli, type GitLabCliError, type GitLabMergeRequestSummary } from "./GitLabCli.ts";
5+import { SourceControlProvider, type SourceControlRefSelector } from "./SourceControlProvider.ts";
6+
7+function providerError(operation: string, cause: GitLabCliError): SourceControlProviderError {
8+ return new SourceControlProviderError({
9+ provider: "gitlab",
10+ operation,
11+ detail: cause.detail,
12+ cause,
13+ });
14+}
15+
16+function 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+
38+function 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+
52+export 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+
106+export const layer = Layer.effect(SourceControlProvider, make());