History

feat(source-control): add Bitbucket & Azure Devops providers (#2473)

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

Commit 91a03e01 parent d501ebe59 files · +5834 −2327Browse files

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

Unified · Split

apps/server/src/sourceControl/BitbucketSourceControlProvider.ts+126−0
@@ -0,0 +1,126 @@
1import { Effect, Layer, Option } from "effect";
2import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts";
3
4import * as BitbucketApi from "./BitbucketApi.ts";
5import * as BitbucketPullRequests from "./bitbucketPullRequests.ts";
6import * as SourceControlProvider from "./SourceControlProvider.ts";
7import type * as SourceControlProviderDiscovery from "./SourceControlProviderDiscovery.ts";
8
9function providerError(
10 operation: string,
11 cause: BitbucketApi.BitbucketApiError,
12): SourceControlProviderError {
13 return new SourceControlProviderError({
14 provider: "bitbucket",
15 operation,
16 detail: cause.detail,
17 cause,
18 });
19}
20
21function toChangeRequest(
22 summary: BitbucketPullRequests.NormalizedBitbucketPullRequestRecord,
23): ChangeRequest {
24 return {
25 provider: "bitbucket",
26 number: summary.number,
27 title: summary.title,
28 url: summary.url,
29 baseRefName: summary.baseRefName,
30 headRefName: summary.headRefName,
31 state: summary.state,
32 updatedAt: summary.updatedAt ?? Option.none(),
33 ...(summary.isCrossRepository !== undefined
34 ? { isCrossRepository: summary.isCrossRepository }
35 : {}),
36 ...(summary.headRepositoryNameWithOwner !== undefined
37 ? { headRepositoryNameWithOwner: summary.headRepositoryNameWithOwner }
38 : {}),
39 ...(summary.headRepositoryOwnerLogin !== undefined
40 ? { headRepositoryOwnerLogin: summary.headRepositoryOwnerLogin }
41 : {}),
42 };
43}
44
45export const make = Effect.fn("makeBitbucketSourceControlProvider")(function* () {
46 const bitbucket = yield* BitbucketApi.BitbucketApi;
47
48 return SourceControlProvider.SourceControlProvider.of({
49 kind: "bitbucket",
50 listChangeRequests: (input) => {
51 const source = SourceControlProvider.sourceControlRefFromInput(input);
52 return bitbucket
53 .listPullRequests({
54 cwd: input.cwd,
55 ...(input.context ? { context: input.context } : {}),
56 headSelector: input.headSelector,
57 ...(source ? { source } : {}),
58 state: input.state,
59 ...(input.limit !== undefined ? { limit: input.limit } : {}),
60 })
61 .pipe(
62 Effect.map((items) => items.map(toChangeRequest)),
63 Effect.mapError((error) => providerError("listChangeRequests", error)),
64 );
65 },
66 getChangeRequest: (input) =>
67 bitbucket.getPullRequest(input).pipe(
68 Effect.map(toChangeRequest),
69 Effect.mapError((error) => providerError("getChangeRequest", error)),
70 ),
71 createChangeRequest: (input) => {
72 const source = SourceControlProvider.sourceControlRefFromInput(input);
73 return bitbucket
74 .createPullRequest({
75 cwd: input.cwd,
76 ...(input.context ? { context: input.context } : {}),
77 baseBranch: input.baseRefName,
78 headSelector: input.headSelector,
79 ...(source ? { source } : {}),
80 ...(input.target ? { target: input.target } : {}),
81 title: input.title,
82 bodyFile: input.bodyFile,
83 })
84 .pipe(Effect.mapError((error) => providerError("createChangeRequest", error)));
85 },
86 getRepositoryCloneUrls: (input) =>
87 bitbucket
88 .getRepositoryCloneUrls(input)
89 .pipe(Effect.mapError((error) => providerError("getRepositoryCloneUrls", error))),
90 createRepository: (input) =>
91 bitbucket
92 .createRepository(input)
93 .pipe(Effect.mapError((error) => providerError("createRepository", error))),
94 getDefaultBranch: (input) =>
95 bitbucket
96 .getDefaultBranch({
97 cwd: input.cwd,
98 ...(input.context ? { context: input.context } : {}),
99 })
100 .pipe(Effect.mapError((error) => providerError("getDefaultBranch", error))),
101 checkoutChangeRequest: (input) =>
102 bitbucket
103 .checkoutPullRequest({
104 cwd: input.cwd,
105 ...(input.context ? { context: input.context } : {}),
106 reference: input.reference,
107 ...(input.force !== undefined ? { force: input.force } : {}),
108 })
109 .pipe(Effect.mapError((error) => providerError("checkoutChangeRequest", error))),
110 });
111});
112
113export const layer = Layer.effect(SourceControlProvider.SourceControlProvider, make());
114
115export const makeDiscovery = Effect.fn("makeBitbucketSourceControlProviderDiscovery")(function* () {
116 const bitbucket = yield* BitbucketApi.BitbucketApi;
117
118 return {
119 type: "api",
120 kind: "bitbucket",
121 label: "Bitbucket",
122 installHint:
123 "Set T3CODE_BITBUCKET_EMAIL and T3CODE_BITBUCKET_API_TOKEN on the server (use a Bitbucket API token with pull request and repository scopes).",
124 probeAuth: bitbucket.probeAuth,
125 } satisfies SourceControlProviderDiscovery.SourceControlApiDiscoverySpec;
126});