import * as Config from "effect/Config";import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Schema from "effect/Schema";import { NonNegativeInt, TrimmedNonEmptyString, type SourceControlProviderAuth, type SourceControlRepositoryCloneUrls, type SourceControlRepositoryVisibility,} from "@t3tools/contracts";import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";import { sanitizeBranchFragment } from "@t3tools/shared/git";import { detectSourceControlProviderFromRemoteUrl } from "@t3tools/shared/sourceControl"; import { BitbucketPullRequestListSchema, BitbucketPullRequestSchema, normalizeBitbucketPullRequestRecord, type NormalizedBitbucketPullRequestRecord,} from "./bitbucketPullRequests.ts";import * as SourceControlProvider from "./SourceControlProvider.ts";import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; const DEFAULT_API_BASE_URL = "https://api.bitbucket.org/2.0"; const BitbucketApiEnvConfig = Config.all({ baseUrl: Config.string("T3CODE_BITBUCKET_API_BASE_URL").pipe( Config.withDefault(DEFAULT_API_BASE_URL), ), accessToken: Config.string("T3CODE_BITBUCKET_ACCESS_TOKEN").pipe(Config.option), email: Config.string("T3CODE_BITBUCKET_EMAIL").pipe(Config.option), apiToken: Config.string("T3CODE_BITBUCKET_API_TOKEN").pipe(Config.option),}); const BitbucketApiOperation = Schema.Literals([ "resolveRepository", "getRepository", "getBranchingModel", "getPullRequest", "listPullRequests", "createRepository", "createPullRequest", "probeAuth", "checkoutPullRequest",]);type BitbucketApiOperation = typeof BitbucketApiOperation.Type; export class BitbucketRepositoryLocatorError extends Schema.TaggedErrorClass<BitbucketRepositoryLocatorError>()( "BitbucketRepositoryLocatorError", { repository: Schema.String, },) { override get message(): string { return "Bitbucket API failed in createRepository: Bitbucket repositories must be specified as workspace/repository."; }} export class BitbucketRequestError extends Schema.TaggedErrorClass<BitbucketRequestError>()( "BitbucketRequestError", { operation: BitbucketApiOperation, cause: Schema.Defect(), },) { override get message(): string { return `Bitbucket API failed in ${this.operation}: Failed to send the Bitbucket request.`; }} export class BitbucketResponseError extends Schema.TaggedErrorClass<BitbucketResponseError>()( "BitbucketResponseError", { operation: BitbucketApiOperation, status: Schema.Int, responseBodyLength: NonNegativeInt, },) { override get message(): string { return `Bitbucket API failed in ${this.operation}: Bitbucket returned HTTP ${this.status}.`; }} export class BitbucketResponseBodyReadError extends Schema.TaggedErrorClass<BitbucketResponseBodyReadError>()( "BitbucketResponseBodyReadError", { operation: BitbucketApiOperation, status: Schema.Int, cause: Schema.Defect(), },) { override get message(): string { return `Bitbucket API failed in ${this.operation}: Bitbucket returned HTTP ${this.status}.`; }} export class BitbucketResponseDecodeError extends Schema.TaggedErrorClass<BitbucketResponseDecodeError>()( "BitbucketResponseDecodeError", { operation: BitbucketApiOperation, status: Schema.Int, cause: Schema.Defect(), },) { override get message(): string { return `Bitbucket API failed in ${this.operation}: Bitbucket returned invalid JSON for the requested resource.`; }} export class BitbucketRepositoryVcsResolveError extends Schema.TaggedErrorClass<BitbucketRepositoryVcsResolveError>()( "BitbucketRepositoryVcsResolveError", { cwd: Schema.String, cause: Schema.Defect(), },) { override get message(): string { return `Bitbucket API failed in resolveRepository: Failed to resolve VCS repository for ${this.cwd}.`; }} export class BitbucketRepositoryRemotesListError extends Schema.TaggedErrorClass<BitbucketRepositoryRemotesListError>()( "BitbucketRepositoryRemotesListError", { cwd: Schema.String, cause: Schema.Defect(), },) { override get message(): string { return `Bitbucket API failed in resolveRepository: Failed to list remotes for ${this.cwd}.`; }} export class BitbucketRepositoryRemoteNotFoundError extends Schema.TaggedErrorClass<BitbucketRepositoryRemoteNotFoundError>()( "BitbucketRepositoryRemoteNotFoundError", { cwd: Schema.String, },) { override get message(): string { return `Bitbucket API failed in resolveRepository: No Bitbucket repository remote was detected for ${this.cwd}.`; }} export class BitbucketPullRequestBodyReadError extends Schema.TaggedErrorClass<BitbucketPullRequestBodyReadError>()( "BitbucketPullRequestBodyReadError", { cwd: Schema.String, bodyFile: Schema.String, cause: Schema.Defect(), },) { override get message(): string { return `Bitbucket API failed in createPullRequest: Failed to read pull request body file ${this.bodyFile}.`; }} export class BitbucketCheckoutError extends Schema.TaggedErrorClass<BitbucketCheckoutError>()( "BitbucketCheckoutError", { cwd: Schema.String, reference: Schema.String, cause: Schema.Defect(), },) { override get message(): string { return "Bitbucket API failed in checkoutPullRequest: Failed to check out the Bitbucket pull request."; }} export const BitbucketApiError = Schema.Union([ BitbucketRepositoryLocatorError, BitbucketRequestError, BitbucketResponseError, BitbucketResponseBodyReadError, BitbucketResponseDecodeError, BitbucketRepositoryVcsResolveError, BitbucketRepositoryRemotesListError, BitbucketRepositoryRemoteNotFoundError, BitbucketPullRequestBodyReadError, BitbucketCheckoutError,]);export type BitbucketApiError = typeof BitbucketApiError.Type;export const isBitbucketApiError = Schema.is(BitbucketApiError); const RawBitbucketRepositorySchema = Schema.Struct({ full_name: TrimmedNonEmptyString, links: Schema.Struct({ html: Schema.optional( Schema.Struct({ href: TrimmedNonEmptyString, }), ), clone: Schema.optional( Schema.Array( Schema.Struct({ name: TrimmedNonEmptyString, href: TrimmedNonEmptyString, }), ), ), }), mainbranch: Schema.optional( Schema.NullOr( Schema.Struct({ name: TrimmedNonEmptyString, }), ), ),}); const RawBitbucketBranchingModelSchema = Schema.Struct({ development: Schema.optional( Schema.Struct({ branch: Schema.optional( Schema.NullOr( Schema.Struct({ name: Schema.optional(TrimmedNonEmptyString), }), ), ), is_valid: Schema.optional(Schema.Boolean), name: Schema.optional(Schema.NullOr(Schema.String)), use_mainbranch: Schema.optional(Schema.Boolean), }), ),}); const BitbucketUserSchema = Schema.Struct({ username: Schema.optional(TrimmedNonEmptyString), display_name: Schema.optional(TrimmedNonEmptyString), account_id: Schema.optional(TrimmedNonEmptyString),}); export interface BitbucketRepositoryLocator { readonly workspace: string; readonly repoSlug: string;} export class BitbucketApi extends Context.Service< BitbucketApi, { readonly probeAuth: Effect.Effect<SourceControlProviderAuth, never>; readonly listPullRequests: (input: { readonly cwd: string; readonly context?: SourceControlProvider.SourceControlProviderContext; readonly headSelector: string; readonly source?: SourceControlProvider.SourceControlRefSelector; readonly state: "open" | "closed" | "merged" | "all"; readonly limit?: number; }) => Effect.Effect<ReadonlyArray<NormalizedBitbucketPullRequestRecord>, BitbucketApiError>; readonly getPullRequest: (input: { readonly cwd: string; readonly context?: SourceControlProvider.SourceControlProviderContext; readonly reference: string; }) => Effect.Effect<NormalizedBitbucketPullRequestRecord, BitbucketApiError>; readonly getRepositoryCloneUrls: (input: { readonly cwd: string; readonly context?: SourceControlProvider.SourceControlProviderContext; readonly repository: string; }) => Effect.Effect<SourceControlRepositoryCloneUrls, BitbucketApiError>; readonly createRepository: (input: { readonly cwd: string; readonly repository: string; readonly visibility: SourceControlRepositoryVisibility; }) => Effect.Effect<SourceControlRepositoryCloneUrls, BitbucketApiError>; readonly createPullRequest: (input: { readonly cwd: string; readonly context?: SourceControlProvider.SourceControlProviderContext; readonly baseBranch: string; readonly headSelector: string; readonly source?: SourceControlProvider.SourceControlRefSelector; readonly target?: SourceControlProvider.SourceControlRefSelector; readonly title: string; readonly bodyFile: string; }) => Effect.Effect<void, BitbucketApiError>; readonly getDefaultBranch: (input: { readonly cwd: string; readonly context?: SourceControlProvider.SourceControlProviderContext; }) => Effect.Effect<string | null, BitbucketApiError>; readonly checkoutPullRequest: (input: { readonly cwd: string; readonly context?: SourceControlProvider.SourceControlProviderContext; readonly reference: string; readonly force?: boolean; }) => Effect.Effect<void, BitbucketApiError>; }>()("t3/sourceControl/BitbucketApi") {} function nonEmpty(value: string | undefined): Option.Option<string> { const trimmed = value?.trim(); return trimmed === undefined || trimmed.length === 0 ? Option.none() : Option.some(trimmed);} function normalizeChangeRequestId(reference: string): string { const trimmed = reference.trim().replace(/^#/, ""); const urlMatch = /(?:pull-requests|pullrequests|pull-request|pull|pr)\/(\d+)(?:\D.*)?$/i.exec( trimmed, ); return urlMatch?.[1] ?? trimmed;} function sourceWorkspace(input: { readonly headSelector: string; readonly source?: SourceControlProvider.SourceControlRefSelector;}): string | undefined { if (input.source?.owner) return input.source.owner; return SourceControlProvider.parseSourceControlOwnerRef(input.headSelector)?.owner;} function toBitbucketStates(state: "open" | "closed" | "merged" | "all"): ReadonlyArray<string> { switch (state) { case "open": return ["OPEN"]; case "closed": return ["DECLINED", "SUPERSEDED"]; case "merged": return ["MERGED"]; case "all": return ["OPEN", "MERGED", "DECLINED", "SUPERSEDED"]; }} function bitbucketQueryString(filters: ReadonlyArray<string>): string { return filters.join(" AND ");} function bitbucketStateFilter(states: ReadonlyArray<string>): string { return states.length === 1 ? `state = "${states[0]}"` : `(${states.map((state) => `state = "${state}"`).join(" OR ")})`;} function parseBitbucketRepositorySlug(value: string): BitbucketRepositoryLocator | null { const normalized = value.trim().replace(/\.git$/u, ""); const parts = normalized.split("/").filter((part) => part.length > 0); if (parts.length < 2) return null; const workspace = parts.at(-2); const repoSlug = parts.at(-1); return workspace && repoSlug ? { workspace, repoSlug } : null;} function requireRepositoryLocator( repository: string,): Effect.Effect<BitbucketRepositoryLocator, BitbucketApiError> { const locator = parseBitbucketRepositorySlug(repository); return locator ? Effect.succeed(locator) : Effect.fail( new BitbucketRepositoryLocatorError({ repository, }), );} function parseBitbucketRemoteUrl(remoteUrl: string): BitbucketRepositoryLocator | null { const trimmed = remoteUrl.trim(); if (trimmed.startsWith("git@")) { const pathStart = trimmed.indexOf(":"); return pathStart < 0 ? null : parseBitbucketRepositorySlug(trimmed.slice(pathStart + 1)); } try { return parseBitbucketRepositorySlug(new URL(trimmed).pathname); } catch { return null; }} function normalizeRepositoryCloneUrls( raw: typeof RawBitbucketRepositorySchema.Type,): SourceControlRepositoryCloneUrls { const httpClone = raw.links.clone?.find((entry) => entry.name.toLowerCase() === "https")?.href ?? raw.links.html?.href; const sshClone = raw.links.clone?.find((entry) => entry.name.toLowerCase() === "ssh")?.href; return { nameWithOwner: raw.full_name, url: httpClone ?? raw.links.html?.href ?? raw.full_name, sshUrl: sshClone ?? httpClone ?? raw.full_name, };} function defaultChangeRequestTargetBranch(input: { readonly repository: typeof RawBitbucketRepositorySchema.Type; readonly branchingModel: typeof RawBitbucketBranchingModelSchema.Type | null;}): string | null { const repositoryMainBranch = input.repository.mainbranch?.name ?? null; const development = input.branchingModel?.development; if (!development || development.use_mainbranch === true || development.is_valid === false) { return repositoryMainBranch; } const developmentBranch = development.branch?.name?.trim() ?? development.name?.trim() ?? ""; if (developmentBranch.length === 0 || developmentBranch === "null") { return repositoryMainBranch; } return developmentBranch;} function shouldPreferSshRemote(originRemoteUrl: string | null): boolean { const trimmed = originRemoteUrl?.trim() ?? ""; return trimmed.startsWith("git@") || trimmed.startsWith("ssh://");} function selectCloneUrl(input: { readonly cloneUrls: SourceControlRepositoryCloneUrls; readonly originRemoteUrl: string | null;}): string { return shouldPreferSshRemote(input.originRemoteUrl) ? input.cloneUrls.sshUrl : input.cloneUrls.url;} function checkoutBranchName(input: { readonly pullRequestId: number; readonly headBranch: string; readonly isCrossRepository: boolean;}): string { if (!input.isCrossRepository) { return input.headBranch; } return `t3code/pr-${input.pullRequestId}/${sanitizeBranchFragment(input.headBranch)}`;} function repositoryNameWithOwner( repository: Schema.Schema.Type<typeof BitbucketPullRequestSchema>["source"]["repository"],): string | null { const fullName = repository?.full_name?.trim() ?? ""; return fullName.length > 0 ? fullName : null;} function repositoryOwnerName(repositoryName: string): string { return repositoryName.split("/")[0]?.trim() || "bitbucket";} function authFromConfig( config: Config.Success<typeof BitbucketApiEnvConfig>,): SourceControlProviderAuth { if (Option.isSome(config.accessToken)) { return { status: "unknown", account: Option.none(), host: Option.some("bitbucket.org"), detail: Option.some("Bitbucket access token is configured."), }; } if (Option.isSome(config.email) && Option.isSome(config.apiToken)) { return { status: "unknown", account: config.email, host: Option.some("bitbucket.org"), detail: Option.some("Bitbucket API token is configured."), }; } return { status: "unauthenticated", account: Option.none(), host: Option.some("bitbucket.org"), detail: Option.some( "Set T3CODE_BITBUCKET_EMAIL and T3CODE_BITBUCKET_API_TOKEN, or T3CODE_BITBUCKET_ACCESS_TOKEN.", ), };} function responseError( operation: BitbucketApiOperation, response: HttpClientResponse.HttpClientResponse,): Effect.Effect<never, BitbucketApiError> { return response.text.pipe( Effect.mapError( (cause) => new BitbucketResponseBodyReadError({ operation, status: response.status, cause, }), ), Effect.flatMap((body) => Effect.fail( new BitbucketResponseError({ operation, status: response.status, responseBodyLength: body.length, }), ), ), );} Lines 1–500 of 891. Load more
import * as Config from "effect/Config"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import { NonNegativeInt, TrimmedNonEmptyString, type SourceControlProviderAuth, type SourceControlRepositoryCloneUrls, type SourceControlRepositoryVisibility, } from "@t3tools/contracts"; import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; import { sanitizeBranchFragment } from "@t3tools/shared/git"; import { detectSourceControlProviderFromRemoteUrl } from "@t3tools/shared/sourceControl"; import { BitbucketPullRequestListSchema, BitbucketPullRequestSchema, normalizeBitbucketPullRequestRecord, type NormalizedBitbucketPullRequestRecord, } from "./bitbucketPullRequests.ts"; import * as SourceControlProvider from "./SourceControlProvider.ts"; import * as GitVcsDriver from "../vcs/GitVcsDriver.ts"; import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; const DEFAULT_API_BASE_URL = "https://api.bitbucket.org/2.0"; const BitbucketApiEnvConfig = Config.all({ baseUrl: Config.string("T3CODE_BITBUCKET_API_BASE_URL").pipe( Config.withDefault(DEFAULT_API_BASE_URL), ), accessToken: Config.string("T3CODE_BITBUCKET_ACCESS_TOKEN").pipe(Config.option), email: Config.string("T3CODE_BITBUCKET_EMAIL").pipe(Config.option), apiToken: Config.string("T3CODE_BITBUCKET_API_TOKEN").pipe(Config.option), }); const BitbucketApiOperation = Schema.Literals([ "resolveRepository", "getRepository", "getBranchingModel", "getPullRequest", "listPullRequests", "createRepository", "createPullRequest", "probeAuth", "checkoutPullRequest", ]); type BitbucketApiOperation = typeof BitbucketApiOperation.Type; export class BitbucketRepositoryLocatorError extends Schema.TaggedErrorClass<BitbucketRepositoryLocatorError>()( "BitbucketRepositoryLocatorError", { repository: Schema.String, }, ) { override get message(): string { return "Bitbucket API failed in createRepository: Bitbucket repositories must be specified as workspace/repository."; } } export class BitbucketRequestError extends Schema.TaggedErrorClass<BitbucketRequestError>()( "BitbucketRequestError", { operation: BitbucketApiOperation, cause: Schema.Defect(), }, ) { override get message(): string { return `Bitbucket API failed in ${this.operation}: Failed to send the Bitbucket request.`; } } export class BitbucketResponseError extends Schema.TaggedErrorClass<BitbucketResponseError>()( "BitbucketResponseError", { operation: BitbucketApiOperation, status: Schema.Int, responseBodyLength: NonNegativeInt, }, ) { override get message(): string { return `Bitbucket API failed in ${this.operation}: Bitbucket returned HTTP ${this.status}.`; } } export class BitbucketResponseBodyReadError extends Schema.TaggedErrorClass<BitbucketResponseBodyReadError>()( "BitbucketResponseBodyReadError", { operation: BitbucketApiOperation, status: Schema.Int, cause: Schema.Defect(), }, ) { override get message(): string { return `Bitbucket API failed in ${this.operation}: Bitbucket returned HTTP ${this.status}.`; } } export class BitbucketResponseDecodeError extends Schema.TaggedErrorClass<BitbucketResponseDecodeError>()( "BitbucketResponseDecodeError", { operation: BitbucketApiOperation, status: Schema.Int, cause: Schema.Defect(), }, ) { override get message(): string { return `Bitbucket API failed in ${this.operation}: Bitbucket returned invalid JSON for the requested resource.`; } } export class BitbucketRepositoryVcsResolveError extends Schema.TaggedErrorClass<BitbucketRepositoryVcsResolveError>()( "BitbucketRepositoryVcsResolveError", { cwd: Schema.String, cause: Schema.Defect(), }, ) { override get message(): string { return `Bitbucket API failed in resolveRepository: Failed to resolve VCS repository for ${this.cwd}.`; } } export class BitbucketRepositoryRemotesListError extends Schema.TaggedErrorClass<BitbucketRepositoryRemotesListError>()( "BitbucketRepositoryRemotesListError", { cwd: Schema.String, cause: Schema.Defect(), }, ) { override get message(): string { return `Bitbucket API failed in resolveRepository: Failed to list remotes for ${this.cwd}.`; } } export class BitbucketRepositoryRemoteNotFoundError extends Schema.TaggedErrorClass<BitbucketRepositoryRemoteNotFoundError>()( "BitbucketRepositoryRemoteNotFoundError", { cwd: Schema.String, }, ) { override get message(): string { return `Bitbucket API failed in resolveRepository: No Bitbucket repository remote was detected for ${this.cwd}.`; } } export class BitbucketPullRequestBodyReadError extends Schema.TaggedErrorClass<BitbucketPullRequestBodyReadError>()( "BitbucketPullRequestBodyReadError", { cwd: Schema.String, bodyFile: Schema.String, cause: Schema.Defect(), }, ) { override get message(): string { return `Bitbucket API failed in createPullRequest: Failed to read pull request body file ${this.bodyFile}.`; } } export class BitbucketCheckoutError extends Schema.TaggedErrorClass<BitbucketCheckoutError>()( "BitbucketCheckoutError", { cwd: Schema.String, reference: Schema.String, cause: Schema.Defect(), }, ) { override get message(): string { return "Bitbucket API failed in checkoutPullRequest: Failed to check out the Bitbucket pull request."; } } export const BitbucketApiError = Schema.Union([ BitbucketRepositoryLocatorError, BitbucketRequestError, BitbucketResponseError, BitbucketResponseBodyReadError, BitbucketResponseDecodeError, BitbucketRepositoryVcsResolveError, BitbucketRepositoryRemotesListError, BitbucketRepositoryRemoteNotFoundError, BitbucketPullRequestBodyReadError, BitbucketCheckoutError, ]); export type BitbucketApiError = typeof BitbucketApiError.Type; export const isBitbucketApiError = Schema.is(BitbucketApiError); const RawBitbucketRepositorySchema = Schema.Struct({ full_name: TrimmedNonEmptyString, links: Schema.Struct({ html: Schema.optional( Schema.Struct({ href: TrimmedNonEmptyString, }), ), clone: Schema.optional( Schema.Array( Schema.Struct({ name: TrimmedNonEmptyString, href: TrimmedNonEmptyString, }), ), ), }), mainbranch: Schema.optional( Schema.NullOr( Schema.Struct({ name: TrimmedNonEmptyString, }), ), ), }); const RawBitbucketBranchingModelSchema = Schema.Struct({ development: Schema.optional( Schema.Struct({ branch: Schema.optional( Schema.NullOr( Schema.Struct({ name: Schema.optional(TrimmedNonEmptyString), }), ), ), is_valid: Schema.optional(Schema.Boolean), name: Schema.optional(Schema.NullOr(Schema.String)), use_mainbranch: Schema.optional(Schema.Boolean), }), ), }); const BitbucketUserSchema = Schema.Struct({ username: Schema.optional(TrimmedNonEmptyString), display_name: Schema.optional(TrimmedNonEmptyString), account_id: Schema.optional(TrimmedNonEmptyString), }); export interface BitbucketRepositoryLocator { readonly workspace: string; readonly repoSlug: string; } export class BitbucketApi extends Context.Service< BitbucketApi, { readonly probeAuth: Effect.Effect<SourceControlProviderAuth, never>; readonly listPullRequests: (input: { readonly cwd: string; readonly context?: SourceControlProvider.SourceControlProviderContext; readonly headSelector: string; readonly source?: SourceControlProvider.SourceControlRefSelector; readonly state: "open" | "closed" | "merged" | "all"; readonly limit?: number; }) => Effect.Effect<ReadonlyArray<NormalizedBitbucketPullRequestRecord>, BitbucketApiError>; readonly getPullRequest: (input: { readonly cwd: string; readonly context?: SourceControlProvider.SourceControlProviderContext; readonly reference: string; }) => Effect.Effect<NormalizedBitbucketPullRequestRecord, BitbucketApiError>; readonly getRepositoryCloneUrls: (input: { readonly cwd: string; readonly context?: SourceControlProvider.SourceControlProviderContext; readonly repository: string; }) => Effect.Effect<SourceControlRepositoryCloneUrls, BitbucketApiError>; readonly createRepository: (input: { readonly cwd: string; readonly repository: string; readonly visibility: SourceControlRepositoryVisibility; }) => Effect.Effect<SourceControlRepositoryCloneUrls, BitbucketApiError>; readonly createPullRequest: (input: { readonly cwd: string; readonly context?: SourceControlProvider.SourceControlProviderContext; readonly baseBranch: string; readonly headSelector: string; readonly source?: SourceControlProvider.SourceControlRefSelector; readonly target?: SourceControlProvider.SourceControlRefSelector; readonly title: string; readonly bodyFile: string; }) => Effect.Effect<void, BitbucketApiError>; readonly getDefaultBranch: (input: { readonly cwd: string; readonly context?: SourceControlProvider.SourceControlProviderContext; }) => Effect.Effect<string | null, BitbucketApiError>; readonly checkoutPullRequest: (input: { readonly cwd: string; readonly context?: SourceControlProvider.SourceControlProviderContext; readonly reference: string; readonly force?: boolean; }) => Effect.Effect<void, BitbucketApiError>; } >()("t3/sourceControl/BitbucketApi") {} function nonEmpty(value: string | undefined): Option.Option<string> { const trimmed = value?.trim(); return trimmed === undefined || trimmed.length === 0 ? Option.none() : Option.some(trimmed); } function normalizeChangeRequestId(reference: string): string { const trimmed = reference.trim().replace(/^#/, ""); const urlMatch = /(?:pull-requests|pullrequests|pull-request|pull|pr)\/(\d+)(?:\D.*)?$/i.exec( trimmed, ); return urlMatch?.[1] ?? trimmed; } function sourceWorkspace(input: { readonly headSelector: string; readonly source?: SourceControlProvider.SourceControlRefSelector; }): string | undefined { if (input.source?.owner) return input.source.owner; return SourceControlProvider.parseSourceControlOwnerRef(input.headSelector)?.owner; } function toBitbucketStates(state: "open" | "closed" | "merged" | "all"): ReadonlyArray<string> { switch (state) { case "open": return ["OPEN"]; case "closed": return ["DECLINED", "SUPERSEDED"]; case "merged": return ["MERGED"]; case "all": return ["OPEN", "MERGED", "DECLINED", "SUPERSEDED"]; } } function bitbucketQueryString(filters: ReadonlyArray<string>): string { return filters.join(" AND "); } function bitbucketStateFilter(states: ReadonlyArray<string>): string { return states.length === 1 ? `state = "${states[0]}"` : `(${states.map((state) => `state = "${state}"`).join(" OR ")})`; } function parseBitbucketRepositorySlug(value: string): BitbucketRepositoryLocator | null { const normalized = value.trim().replace(/\.git$/u, ""); const parts = normalized.split("/").filter((part) => part.length > 0); if (parts.length < 2) return null; const workspace = parts.at(-2); const repoSlug = parts.at(-1); return workspace && repoSlug ? { workspace, repoSlug } : null; } function requireRepositoryLocator( repository: string, ): Effect.Effect<BitbucketRepositoryLocator, BitbucketApiError> { const locator = parseBitbucketRepositorySlug(repository); return locator ? Effect.succeed(locator) : Effect.fail( new BitbucketRepositoryLocatorError({ repository, }), ); } function parseBitbucketRemoteUrl(remoteUrl: string): BitbucketRepositoryLocator | null { const trimmed = remoteUrl.trim(); if (trimmed.startsWith("git@")) { const pathStart = trimmed.indexOf(":"); return pathStart < 0 ? null : parseBitbucketRepositorySlug(trimmed.slice(pathStart + 1)); } try { return parseBitbucketRepositorySlug(new URL(trimmed).pathname); } catch { return null; } } function normalizeRepositoryCloneUrls( raw: typeof RawBitbucketRepositorySchema.Type, ): SourceControlRepositoryCloneUrls { const httpClone = raw.links.clone?.find((entry) => entry.name.toLowerCase() === "https")?.href ?? raw.links.html?.href; const sshClone = raw.links.clone?.find((entry) => entry.name.toLowerCase() === "ssh")?.href; return { nameWithOwner: raw.full_name, url: httpClone ?? raw.links.html?.href ?? raw.full_name, sshUrl: sshClone ?? httpClone ?? raw.full_name, }; } function defaultChangeRequestTargetBranch(input: { readonly repository: typeof RawBitbucketRepositorySchema.Type; readonly branchingModel: typeof RawBitbucketBranchingModelSchema.Type | null; }): string | null { const repositoryMainBranch = input.repository.mainbranch?.name ?? null; const development = input.branchingModel?.development; if (!development || development.use_mainbranch === true || development.is_valid === false) { return repositoryMainBranch; } const developmentBranch = development.branch?.name?.trim() ?? development.name?.trim() ?? ""; if (developmentBranch.length === 0 || developmentBranch === "null") { return repositoryMainBranch; } return developmentBranch; } function shouldPreferSshRemote(originRemoteUrl: string | null): boolean { const trimmed = originRemoteUrl?.trim() ?? ""; return trimmed.startsWith("git@") || trimmed.startsWith("ssh://"); } function selectCloneUrl(input: { readonly cloneUrls: SourceControlRepositoryCloneUrls; readonly originRemoteUrl: string | null; }): string { return shouldPreferSshRemote(input.originRemoteUrl) ? input.cloneUrls.sshUrl : input.cloneUrls.url; } function checkoutBranchName(input: { readonly pullRequestId: number; readonly headBranch: string; readonly isCrossRepository: boolean; }): string { if (!input.isCrossRepository) { return input.headBranch; } return `t3code/pr-${input.pullRequestId}/${sanitizeBranchFragment(input.headBranch)}`; } function repositoryNameWithOwner( repository: Schema.Schema.Type<typeof BitbucketPullRequestSchema>["source"]["repository"], ): string | null { const fullName = repository?.full_name?.trim() ?? ""; return fullName.length > 0 ? fullName : null; } function repositoryOwnerName(repositoryName: string): string { return repositoryName.split("/")[0]?.trim() || "bitbucket"; } function authFromConfig( config: Config.Success<typeof BitbucketApiEnvConfig>, ): SourceControlProviderAuth { if (Option.isSome(config.accessToken)) { return { status: "unknown", account: Option.none(), host: Option.some("bitbucket.org"), detail: Option.some("Bitbucket access token is configured."), }; } if (Option.isSome(config.email) && Option.isSome(config.apiToken)) { return { status: "unknown", account: config.email, host: Option.some("bitbucket.org"), detail: Option.some("Bitbucket API token is configured."), }; } return { status: "unauthenticated", account: Option.none(), host: Option.some("bitbucket.org"), detail: Option.some( "Set T3CODE_BITBUCKET_EMAIL and T3CODE_BITBUCKET_API_TOKEN, or T3CODE_BITBUCKET_ACCESS_TOKEN.", ), }; } function responseError( operation: BitbucketApiOperation, response: HttpClientResponse.HttpClientResponse, ): Effect.Effect<never, BitbucketApiError> { return response.text.pipe( Effect.mapError( (cause) => new BitbucketResponseBodyReadError({ operation, status: response.status, cause, }), ), Effect.flatMap((body) => Effect.fail( new BitbucketResponseError({ operation, status: response.status, responseBodyLength: body.length, }), ), ), ); }