diff --git a/lib/constants/platforms.ts b/lib/constants/platforms.ts index 049155bd3ba43c..47f16640debaa1 100644 --- a/lib/constants/platforms.ts +++ b/lib/constants/platforms.ts @@ -26,4 +26,8 @@ export const GITLAB_API_USING_HOST_TYPES = [ 'gitlab-changelog', ]; -export const BITBUCKET_API_USING_HOST_TYPES = ['bitbucket', 'bitbucket-tags']; +export const BITBUCKET_API_USING_HOST_TYPES = [ + 'bitbucket', + 'bitbucket-changelog', + 'bitbucket-tags', +]; diff --git a/lib/modules/platform/bitbucket/schema.ts b/lib/modules/platform/bitbucket/schema.ts new file mode 100644 index 00000000000000..48104f2dc55e90 --- /dev/null +++ b/lib/modules/platform/bitbucket/schema.ts @@ -0,0 +1,22 @@ +import { z } from 'zod'; + +const BitbucketSourceTypeSchema = z.enum(['commit_directory', 'commit_file']); + +const SourceResultsSchema = z.object({ + path: z.string(), + type: BitbucketSourceTypeSchema, + commit: z.object({ + hash: z.string(), + }), +}); + +const PagedSchema = z.object({ + page: z.number().optional(), + pagelen: z.number(), + size: z.number().optional(), + next: z.string().optional(), +}); + +export const PagedSourceResultsSchema = PagedSchema.extend({ + values: z.array(SourceResultsSchema), +}); diff --git a/lib/workers/repository/update/pr/changelog/api.ts b/lib/workers/repository/update/pr/changelog/api.ts index 55809bef985099..d4fa3edb6da822 100644 --- a/lib/workers/repository/update/pr/changelog/api.ts +++ b/lib/workers/repository/update/pr/changelog/api.ts @@ -1,3 +1,4 @@ +import { BitbucketChangeLogSource } from './bitbucket/source'; import type { ChangeLogSource } from './source'; import { GitHubChangeLogSource } from './source-github'; import { GitLabChangeLogSource } from './source-gitlab'; @@ -5,5 +6,6 @@ import { GitLabChangeLogSource } from './source-gitlab'; const api = new Map(); export default api; +api.set('bitbucket', new BitbucketChangeLogSource()); api.set('github', new GitHubChangeLogSource()); api.set('gitlab', new GitLabChangeLogSource()); diff --git a/lib/workers/repository/update/pr/changelog/bitbucket/index.spec.ts b/lib/workers/repository/update/pr/changelog/bitbucket/index.spec.ts new file mode 100644 index 00000000000000..a0ba419de79d14 --- /dev/null +++ b/lib/workers/repository/update/pr/changelog/bitbucket/index.spec.ts @@ -0,0 +1,112 @@ +import type { ChangeLogProject, ChangeLogRelease } from '..'; +import { Fixtures } from '../../../../../../../test/fixtures'; +import * as httpMock from '../../../../../../../test/http-mock'; +import { partial } from '../../../../../../../test/util'; +import type { BranchUpgradeConfig } from '../../../../../types'; +import { getReleaseList, getReleaseNotesMdFile } from '../release-notes'; +import { BitbucketChangeLogSource } from './source'; + +const baseUrl = 'https://bitbucket.org/'; +const apiBaseUrl = 'https://api.bitbucket.org/'; + +const changelogMd = Fixtures.get('jest.md', '../..'); + +const upgrade = partial({ + manager: 'some-manager', + packageName: 'some-repo', +}); + +const bitbucketTreeResponse = { + values: [ + { + type: 'commit_directory', + path: 'lib', + commit: { + hash: '1234', + }, + }, + { + type: 'commit_file', + path: 'CHANGELOG.md', + commit: { + hash: 'abcd', + }, + }, + { + type: 'commit_file', + path: 'RELEASE_NOTES.md', + commit: { + hash: 'asdf', + }, + }, + ], +}; + +const bitbucketTreeResponseNoChangelogFiles = { + values: [ + { + type: 'commit_directory', + path: 'lib', + commit: { + hash: '1234', + }, + }, + ], +}; + +const bitbucketProject = partial({ + type: 'bitbucket', + repository: 'some-org/some-repo', + baseUrl, + apiBaseUrl, +}); + +describe('workers/repository/update/pr/changelog/bitbucket/index', () => { + it('handles release notes', async () => { + httpMock + .scope(apiBaseUrl) + .get('/2.0/repositories/some-org/some-repo/src?pagelen=100') + .reply(200, bitbucketTreeResponse) + .get('/2.0/repositories/some-org/some-repo/src/abcd/CHANGELOG.md') + .reply(200, changelogMd); + const res = await getReleaseNotesMdFile(bitbucketProject); + + expect(res).toMatchObject({ + changelogFile: 'CHANGELOG.md', + changelogMd: changelogMd + '\n#\n##', + }); + }); + + it('handles missing release notes', async () => { + httpMock + .scope(apiBaseUrl) + .get('/2.0/repositories/some-org/some-repo/src?pagelen=100') + .reply(200, bitbucketTreeResponseNoChangelogFiles); + const res = await getReleaseNotesMdFile(bitbucketProject); + expect(res).toBeNull(); + }); + + it('handles release list', async () => { + const res = await getReleaseList( + bitbucketProject, + partial({}) + ); + expect(res).toBeEmptyArray(); + }); + + describe('source', () => { + it('returns api base url', () => { + const source = new BitbucketChangeLogSource(); + expect(source.getAPIBaseUrl(upgrade)).toBe(apiBaseUrl); + }); + + it('returns get ref comparison url', () => { + const source = new BitbucketChangeLogSource(); + expect( + source.getCompareURL(baseUrl, 'some-org/some-repo', 'abc', 'xzy') + ).toBe( + 'https://bitbucket.org/some-org/some-repo/branches/compare/xzy%0Dabc' + ); + }); + }); +}); diff --git a/lib/workers/repository/update/pr/changelog/bitbucket/index.ts b/lib/workers/repository/update/pr/changelog/bitbucket/index.ts new file mode 100644 index 00000000000000..594f32befa65f0 --- /dev/null +++ b/lib/workers/repository/update/pr/changelog/bitbucket/index.ts @@ -0,0 +1,78 @@ +import is from '@sindresorhus/is'; +import changelogFilenameRegex from 'changelog-filename-regex'; +import { logger } from '../../../../../../logger'; +import { PagedSourceResultsSchema } from '../../../../../../modules/platform/bitbucket/schema'; +import { BitbucketHttp } from '../../../../../../util/http/bitbucket'; +import { joinUrlParts } from '../../../../../../util/url'; +import type { + ChangeLogFile, + ChangeLogNotes, + ChangeLogProject, + ChangeLogRelease, +} from '../types'; + +export const id = 'bitbucket-changelog'; +const bitbucketHttp = new BitbucketHttp(id); + +export async function getReleaseNotesMd( + repository: string, + apiBaseUrl: string, + _sourceDirectory?: string +): Promise { + logger.trace('bitbucket.getReleaseNotesMd()'); + + const repositorySourceURl = joinUrlParts( + apiBaseUrl, + `2.0/repositories`, + repository, + 'src' + ); + + const rootFiles = ( + await bitbucketHttp.getJson( + repositorySourceURl, + { + paginate: true, + }, + PagedSourceResultsSchema + ) + ).body.values; + + const allFiles = rootFiles.filter((f) => f.type === 'commit_file'); + + const files = allFiles.filter((f) => changelogFilenameRegex.test(f.path)); + + const changelogFile = files.shift(); + if (is.nullOrUndefined(changelogFile)) { + logger.trace('no changelog file found'); + return null; + } + + if (files.length !== 0) { + logger.debug( + `Multiple candidates for changelog file, using ${changelogFile.path}` + ); + } + + const fileRes = await bitbucketHttp.get( + joinUrlParts( + repositorySourceURl, + changelogFile.commit.hash, + changelogFile.path + ) + ); + + const changelogMd = `${fileRes.body}\n#\n##`; + return { changelogFile: changelogFile.path, changelogMd }; +} + +export function getReleaseList( + _project: ChangeLogProject, + _release: ChangeLogRelease +): ChangeLogNotes[] { + logger.trace('bitbucket.getReleaseList()'); + logger.info( + 'Unsupported Bitbucket Cloud feature. Skipping release fetching.' + ); + return []; +} diff --git a/lib/workers/repository/update/pr/changelog/bitbucket/source.ts b/lib/workers/repository/update/pr/changelog/bitbucket/source.ts new file mode 100644 index 00000000000000..bfbc12ea6dd85d --- /dev/null +++ b/lib/workers/repository/update/pr/changelog/bitbucket/source.ts @@ -0,0 +1,21 @@ +import type { BranchUpgradeConfig } from '../../../../../types'; +import { ChangeLogSource } from '../source'; + +export class BitbucketChangeLogSource extends ChangeLogSource { + constructor() { + super('bitbucket', 'bitbucket-tags'); + } + + getAPIBaseUrl(_config: BranchUpgradeConfig): string { + return 'https://api.bitbucket.org/'; + } + + getCompareURL( + baseUrl: string, + repository: string, + prevHead: string, + nextHead: string + ): string { + return `${baseUrl}${repository}/branches/compare/${nextHead}%0D${prevHead}`; + } +} diff --git a/lib/workers/repository/update/pr/changelog/release-notes.ts b/lib/workers/repository/update/pr/changelog/release-notes.ts index fa9b565d035ab7..b4db11ab34d740 100644 --- a/lib/workers/repository/update/pr/changelog/release-notes.ts +++ b/lib/workers/repository/update/pr/changelog/release-notes.ts @@ -10,6 +10,7 @@ import { detectPlatform } from '../../../../../util/common'; import { linkify } from '../../../../../util/markdown'; import { newlineRegex, regEx } from '../../../../../util/regex'; import type { BranchUpgradeConfig } from '../../../../types'; +import * as bitbucket from './bitbucket'; import * as github from './github'; import * as gitlab from './gitlab'; import type { @@ -35,7 +36,8 @@ export async function getReleaseList( return await gitlab.getReleaseList(project, release); case 'github': return await github.getReleaseList(project, release); - + case 'bitbucket': + return bitbucket.getReleaseList(project, release); default: logger.warn({ apiBaseUrl, repository, type }, 'Invalid project type'); return []; @@ -262,7 +264,12 @@ export async function getReleaseNotesMdFileInner( apiBaseUrl, sourceDirectory ); - + case 'bitbucket': + return await bitbucket.getReleaseNotesMd( + repository, + apiBaseUrl, + sourceDirectory + ); default: logger.warn({ apiBaseUrl, repository, type }, 'Invalid project type'); return null; diff --git a/lib/workers/repository/update/pr/changelog/source.ts b/lib/workers/repository/update/pr/changelog/source.ts index c9e32568e8de62..7a1498b1837209 100644 --- a/lib/workers/repository/update/pr/changelog/source.ts +++ b/lib/workers/repository/update/pr/changelog/source.ts @@ -23,8 +23,8 @@ export abstract class ChangeLogSource { private cacheNamespace: string; constructor( - platform: 'github' | 'gitlab', - datasource: 'github-tags' | 'gitlab-tags' + platform: 'bitbucket' | 'github' | 'gitlab', + datasource: 'bitbucket-tags' | 'github-tags' | 'gitlab-tags' ) { this.platform = platform; this.datasource = datasource; diff --git a/lib/workers/repository/update/pr/changelog/types.ts b/lib/workers/repository/update/pr/changelog/types.ts index 23c4747a057e13..34f4d359394257 100644 --- a/lib/workers/repository/update/pr/changelog/types.ts +++ b/lib/workers/repository/update/pr/changelog/types.ts @@ -25,7 +25,7 @@ export interface ChangeLogRelease { export interface ChangeLogProject { packageName?: string; - type: 'github' | 'gitlab'; + type: 'bitbucket' | 'github' | 'gitlab'; apiBaseUrl?: string; baseUrl: string; repository: string; @@ -33,7 +33,10 @@ export interface ChangeLogProject { sourceDirectory?: string; } -export type ChangeLogError = 'MissingGithubToken' | 'MissingGitlabToken'; +export type ChangeLogError = + | 'MissingBitbucketToken' + | 'MissingGithubToken' + | 'MissingGitlabToken'; export interface ChangeLogResult { hasReleaseNotes?: boolean;