1import { SourceControlProviderKind } from "@t3tools/contracts";
2import * as Effect from "effect/Effect";
3import * as Option from "effect/Option";
4import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts";
5
6import * as BitbucketApi from "./BitbucketApi.ts";
7import type { NormalizedBitbucketPullRequestRecord } from "./bitbucketPullRequests.ts";
8import * as SourceControlProvider from "@t3tools/source-control-core/server/SourceControlProvider";
9import type { SourceControlApiDiscoverySpec } from "@t3tools/source-control-core/server/discovery";
10
11function toChangeRequest(summary: NormalizedBitbucketPullRequestRecord): ChangeRequest {
12 return {
13 provider: SourceControlProviderKind.make("bitbucket"),
14 number: summary.number,
15 title: summary.title,
16 url: summary.url,
17 baseRefName: summary.baseRefName,
18 headRefName: summary.headRefName,
19 state: summary.state,
20 ...(summary.isDraft === true ? { isDraft: true } : {}),
21 updatedAt: summary.updatedAt ?? Option.none(),
22 ...(summary.isCrossRepository !== undefined
23 ? { isCrossRepository: summary.isCrossRepository }
24 : {}),
25 ...(summary.headRepositoryNameWithOwner !== undefined
26 ? { headRepositoryNameWithOwner: summary.headRepositoryNameWithOwner }
27 : {}),
28 ...(summary.headRepositoryOwnerLogin !== undefined
29 ? { headRepositoryOwnerLogin: summary.headRepositoryOwnerLogin }
30 : {}),
31 };
32}
33
34export const make = Effect.gen(function* () {
35 const bitbucket = yield* BitbucketApi.BitbucketApi;
36
37 return SourceControlProvider.SourceControlProvider.of({
38 kind: SourceControlProviderKind.make("bitbucket"),
39 listChangeRequests: (input) => {
40 const source = SourceControlProvider.sourceControlRefFromInput(input);
41 return bitbucket
42 .listPullRequests({
43 cwd: input.cwd,
44 ...(input.context ? { context: input.context } : {}),
45 headSelector: input.headSelector,
46 ...(source ? { source } : {}),
47 state: input.state,
48 ...(input.limit !== undefined ? { limit: input.limit } : {}),
49 })
50 .pipe(
51 Effect.map((items) => items.map(toChangeRequest)),
52 Effect.mapError(
53 (error) =>
54 new SourceControlProviderError({
55 provider: SourceControlProviderKind.make("bitbucket"),
56 operation: "listChangeRequests",
57 cwd: input.cwd,
58 reference: SourceControlProvider.transportSafeSourceControlErrorValue(
59 input.headSelector,
60 ),
61 detail: "Failed to list change requests.",
62 cause: error,
63 }),
64 ),
65 );
66 },
67 getChangeRequest: (input) =>
68 bitbucket.getPullRequest(input).pipe(
69 Effect.map(toChangeRequest),
70 Effect.mapError(
71 (error) =>
72 new SourceControlProviderError({
73 provider: SourceControlProviderKind.make("bitbucket"),
74 operation: "getChangeRequest",
75 cwd: input.cwd,
76 reference: SourceControlProvider.transportSafeSourceControlErrorValue(
77 input.reference,
78 ),
79 detail: "Failed to get change request.",
80 cause: error,
81 }),
82 ),
83 ),
84 createChangeRequest: (input) => {
85 const source = SourceControlProvider.sourceControlRefFromInput(input);
86 return bitbucket
87 .createPullRequest({
88 cwd: input.cwd,
89 ...(input.context ? { context: input.context } : {}),
90 baseBranch: input.baseRefName,
91 headSelector: input.headSelector,
92 ...(source ? { source } : {}),
93 ...(input.target ? { target: input.target } : {}),
94 title: input.title,
95 bodyFile: input.bodyFile,
96 })
97 .pipe(
98 Effect.mapError(
99 (error) =>
100 new SourceControlProviderError({
101 provider: SourceControlProviderKind.make("bitbucket"),
102 operation: "createChangeRequest",
103 cwd: input.cwd,
104 reference: SourceControlProvider.transportSafeSourceControlErrorValue(
105 input.headSelector,
106 ),
107 detail: "Failed to create change request.",
108 cause: error,
109 }),
110 ),
111 );
112 },
113 getRepositoryCloneUrls: (input) =>
114 bitbucket.getRepositoryCloneUrls(input).pipe(
115 Effect.mapError(
116 (error) =>
117 new SourceControlProviderError({
118 provider: SourceControlProviderKind.make("bitbucket"),
119 operation: "getRepositoryCloneUrls",
120 cwd: input.cwd,
121 repository: SourceControlProvider.transportSafeSourceControlErrorValue(
122 input.repository,
123 ),
124 detail: "Failed to get repository clone URLs.",
125 cause: error,
126 }),
127 ),
128 ),
129 createRepository: (input) =>
130 bitbucket.createRepository(input).pipe(
131 Effect.mapError(
132 (error) =>
133 new SourceControlProviderError({
134 provider: SourceControlProviderKind.make("bitbucket"),
135 operation: "createRepository",
136 cwd: input.cwd,
137 repository: SourceControlProvider.transportSafeSourceControlErrorValue(
138 input.repository,
139 ),
140 detail: "Failed to create repository.",
141 cause: error,
142 }),
143 ),
144 ),
145 getDefaultBranch: (input) =>
146 bitbucket
147 .getDefaultBranch({
148 cwd: input.cwd,
149 ...(input.context ? { context: input.context } : {}),
150 })
151 .pipe(
152 Effect.mapError(
153 (error) =>
154 new SourceControlProviderError({
155 provider: SourceControlProviderKind.make("bitbucket"),
156 operation: "getDefaultBranch",
157 cwd: input.cwd,
158 detail: "Failed to get default branch.",
159 cause: error,
160 }),
161 ),
162 ),
163 checkoutChangeRequest: (input) =>
164 bitbucket
165 .checkoutPullRequest({
166 cwd: input.cwd,
167 ...(input.context ? { context: input.context } : {}),
168 reference: input.reference,
169 ...(input.force !== undefined ? { force: input.force } : {}),
170 })
171 .pipe(
172 Effect.mapError(
173 (error) =>
174 new SourceControlProviderError({
175 provider: SourceControlProviderKind.make("bitbucket"),
176 operation: "checkoutChangeRequest",
177 cwd: input.cwd,
178 reference: SourceControlProvider.transportSafeSourceControlErrorValue(
179 input.reference,
180 ),
181 detail: "Failed to check out change request.",
182 cause: error,
183 }),
184 ),
185 ),
186 });
187});
188
189export const makeDiscovery = Effect.gen(function* () {
190 const bitbucket = yield* BitbucketApi.BitbucketApi;
191
192 return {
193 type: "api",
194 kind: SourceControlProviderKind.make("bitbucket"),
195 label: "Bitbucket",
196 installHint: "Add a Bitbucket token in Settings → Source Control.",
197 probeAuth: bitbucket.probeAuth,
198 } satisfies SourceControlApiDiscoverySpec;
199});