Skip to content

Commit

Permalink
feat(platform/bitbucket): add support for fetching release notes (#22094
Browse files Browse the repository at this point in the history
)

Co-authored-by: Sebastian Poxhofer <[email protected]>
  • Loading branch information
setchy and secustor authored Jul 3, 2023
1 parent e8fb82d commit b3424c6
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 7 deletions.
6 changes: 5 additions & 1 deletion lib/constants/platforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
];
22 changes: 22 additions & 0 deletions lib/modules/platform/bitbucket/schema.ts
Original file line number Diff line number Diff line change
@@ -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),
});
2 changes: 2 additions & 0 deletions lib/workers/repository/update/pr/changelog/api.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { BitbucketChangeLogSource } from './bitbucket/source';
import type { ChangeLogSource } from './source';
import { GitHubChangeLogSource } from './source-github';
import { GitLabChangeLogSource } from './source-gitlab';

const api = new Map<string, ChangeLogSource>();
export default api;

api.set('bitbucket', new BitbucketChangeLogSource());
api.set('github', new GitHubChangeLogSource());
api.set('gitlab', new GitLabChangeLogSource());
112 changes: 112 additions & 0 deletions lib/workers/repository/update/pr/changelog/bitbucket/index.spec.ts
Original file line number Diff line number Diff line change
@@ -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<BranchUpgradeConfig>({
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<ChangeLogProject>({
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<ChangeLogRelease>({})
);
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'
);
});
});
});
78 changes: 78 additions & 0 deletions lib/workers/repository/update/pr/changelog/bitbucket/index.ts
Original file line number Diff line number Diff line change
@@ -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<ChangeLogFile | null> {
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 [];
}
21 changes: 21 additions & 0 deletions lib/workers/repository/update/pr/changelog/bitbucket/source.ts
Original file line number Diff line number Diff line change
@@ -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}`;
}
}
11 changes: 9 additions & 2 deletions lib/workers/repository/update/pr/changelog/release-notes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 [];
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions lib/workers/repository/update/pr/changelog/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 5 additions & 2 deletions lib/workers/repository/update/pr/changelog/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,18 @@ export interface ChangeLogRelease {

export interface ChangeLogProject {
packageName?: string;
type: 'github' | 'gitlab';
type: 'bitbucket' | 'github' | 'gitlab';
apiBaseUrl?: string;
baseUrl: string;
repository: string;
sourceUrl: string;
sourceDirectory?: string;
}

export type ChangeLogError = 'MissingGithubToken' | 'MissingGitlabToken';
export type ChangeLogError =
| 'MissingBitbucketToken'
| 'MissingGithubToken'
| 'MissingGitlabToken';

export interface ChangeLogResult {
hasReleaseNotes?: boolean;
Expand Down

0 comments on commit b3424c6

Please sign in to comment.