feat(core): add Bitbucket Cloud provider - #117
Conversation
REST 2.0 implementation of the Provider interface for bitbucket.org, mirroring the GitLab provider's shape. Partial #5 (Gitea/Forgejo in flight via #111; Sourcehut remains — distinct API, separate PR). Ancestry (the core risk — find-release treats compareCommits(repo, tagSha, targetCommit) status behind|identical as "tag contains commit"): Bitbucket has no GitHub/GitLab-style 4-way compare, so compareCommits uses the commits endpoint's include/exclude set difference: include=head&exclude=base; empty ⟹ head is an ancestor of base ⟹ the base tag CONTAINS head → 'behind'. This mirrors GitLab's from=base&to=head exactly. Direction locked by a URL-asserting test and proven by mutation (swapping include/exclude turns it red, restored green). - getPullRequest: state MERGED + merge_commit.hash (DECLINED/SUPERSEDED → PrNotMergedError; missing hash → PrMergeCommitUnavailableError). - getCommit: /commits/{sha} → hash/date/first-line subject. - listTagsWithDates: /refs/tags?sort=-target.date, follows the body `next` link. - getReleaseNotes: returns null without a call — Bitbucket has no Releases API. - getIssueClosingCommit: best-effort (open/closed_without_fix) — Bitbucket's issue→commit linkage isn't reliably exposed via REST. - containingTags/getTagDate: left unset (no endpoint) → algorithm bisects. Wiring: kind widened to 'bitbucket'; providers/index.ts routes bitbucket.org + isKnownHost + export; parse-input.ts recognizes /commits|src/{sha}, /pull-requests/{N}, /issues/{N} and surfaces a shape error (not unsupported) for unrecognized bitbucket.org URLs. Web Worker routes automatically via core's providerFor (bitbucket.org is a fixed host like github.com — no extra-hosts opt). Co-Authored-By: Claude <noreply@anthropic.com>
lukaso-bot
left a comment
There was a problem hiding this comment.
Blocking
getCommit calls the wrong endpoint (packages/core/src/providers/bitbucket/client.ts:151) — see inline. The plural /commits/{sha} list endpoint returns {values:[...]}; getCommit reads it as a single object, so every Bitbucket commit and PR lookup is broken. One-char fix: /commits/ -> /commit/. The unit test passes only because it mocks the wrong shape too (see inline on the test). Likely copied from GitHub's /commits/{sha}, which does return a single object; Bitbucket's does not.
Not in this diff, but reachable for Bitbucket once merged
Pre-existing github-vs-gitlab binaries; this PR turns them on for Bitbucket without updating them, so the provider ships half-wired:
packages/web/src/auth.ts:22(resolveProviderToken) branches GitHub vs GitLab only. Forbitbucket.orgit falls into the GitLab path: an inboundx-user-gitlab-tokenheader would be forwarded toapi.bitbucket.orgas a Bearer token (cross-host leak, same invariant as theGITLAB_TOKENguard), and there is noBITBUCKET_TOKENpath, so private repos always 401. Add a bitbucket branch (or short-circuit to undefined).packages/cli/src/auth.ts:30(resolveToken) — same binary. Bitbucket runsglab auth status --hostname bitbucket.org(always fails, 2s wasted) and never reads a Bitbucket env var. Private repos can only auth via--token.packages/web/src/routes/issue.tsx:524(renderIssueTransient) —host === 'github.com' ? 'issues' : '-/issues'buildsbitbucket.org/.../-/issues/N, whichparseInputrejects. The normal issue path usesprovider.urls.issue(); this error-render path was missed.packages/web/src/ui/layout.tsx:208— Slack snippet hardcodeshost === 'github.com' ? '#' : '!', so an unreleased Bitbucket PR renders!7(GitLab MR syntax) instead of#7.terms.mergeRequestPrefixis#. Narrow: only the slack format, only unreleased PRs.
Minor / cleanup
BITBUCKET_HOST = 'bitbucket.org'is defined in three files (client.ts:47,parse-input.ts,providers/index.tswhere it's exported). The other two could import the export. Low priority — mirrors the existing GitHub local-def pattern.bitbucket-client.test.ts:urlOf(reads_harnessUrl, which is never set) plus the trailingvoid urlOf;is dead code; the tests read module-levellastUrldirectly. Delete both.
Ancestry direction in compareCommits (include=head, exclude=base, empty -> behind -> contains) verified correct, and locked by the test at line 171.
getCommit called the plural /commits/{sha} list endpoint but read the
response as a single object (body.hash/date/message). The plural endpoint
returns a {values:[...]} envelope, so body.hash was undefined, fullSha
became undefined, and every Bitbucket commit and PR lookup silently
reported "not yet released".
Switch to the singular /commit/{sha} endpoint, which returns the single
commit object the field reads expect. compareCommits and listTagsWithDates
keep the plural list endpoint (they read body.values). Adds a URL guard
test asserting the singular path so it cannot regress.
Co-Authored-By: Claude <noreply@anthropic.com>
|
Won't do until there is demand. |
Adds the Bitbucket Cloud (
bitbucket.org) provider — partial #5 (Gitea/Forgejo are in flight via #111; Sourcehut remains as a distinct API → separate PR).REST 2.0 implementation of the
Providerinterface, mirroring the GitLab provider's shape.pnpm validategreen end-to-end (314 tests).Ancestry direction (the core risk — proven, not assumed)
find-releasecallscompareCommits(repo, tagSha, targetCommit)and treatsbehind | identicalas "the tag contains the commit." Bitbucket has no GitHub/GitLab-style 4-way compare, socompareCommitsuses the commits endpoint'sinclude/excludeset difference:This mirrors GitLab's
from=base&to=head(commits in head-not-in-base) exactly.Mutation proof: a test asserts the requested URL carries
include={head}&exclude={base}AND the status. Flippinginclude/excludemakes it go red (expected … to contain 'include=aaaaaaaa'; status flips); restored → green. Recorded inBitbucketProvider.compareCommits — ANCESTRY DIRECTION.Methods
getPullRequeststate === 'MERGED'+merge_commit.hash;DECLINED/SUPERSEDED→PrNotMergedError; missing hash →PrMergeCommitUnavailableErrorgetCommit/commits/{sha}→hash/date/ first-line subjectlistTagsWithDates/refs/tags?sort=-target.date, follows the bodynextlink (MAX_TAG_PAGES=5, parity with GitLab)compareCommitsinclude=head&exclude=base(above)getReleaseNotesnullwithout a network call — Bitbucket has no Releases APIgetIssueClosingCommitopen/closed_without_fix— Bitbucket's issue→commit linkage isn't reliably exposed via RESTcontainingTags/getTagDateterms = { mergeRequest: 'Pull request', mergeRequestPrefix: '#' }. Rate-limit headers read when present,nullotherwise (Bitbucket emits them inconsistently).Wiring
Provider.kindwidened to'github' | 'gitlab' | 'bitbucket'.providers/index.ts: routesbitbucket.org→makeBitbucketProvider, added toisKnownHost+ the unsupported-host error's supported list, exported.bitbucket.orgis a single fixed host (likegithub.com) — noextraBitbucketHostsopt needed.parse-input.ts: recognizes/commits|src/{sha},/pull-requests/{N},/issues/{N}, and surfaces a shape error (notUnsupportedHostError) for unrecognizedbitbucket.orgURLs.providerFor— no web-side plumbing.Not in this PR (deliberate)