| 1 | import { SourceControlProviderKind } from "@t3tools/contracts"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Option from "effect/Option"; |
| 4 | import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts"; |
| 5 | |
| 6 | import * as BitbucketApi from "./BitbucketApi.ts"; |
| 7 | import type { NormalizedBitbucketPullRequestRecord } from "./bitbucketPullRequests.ts"; |
| 8 | import * as SourceControlProvider from "@t3tools/source-control-core/server/SourceControlProvider"; |
| 9 | import type { SourceControlApiDiscoverySpec } from "@t3tools/source-control-core/server/discovery"; |
| 10 | |
| 11 | function 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 | |
| 34 | export 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 | |
| 189 | export 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 | }); |