diff --git a/plugins/github-release-manager/src/api/PluginApiClient.ts b/plugins/github-release-manager/src/api/PluginApiClient.ts index 4a9404fad5..42b3210823 100644 --- a/plugins/github-release-manager/src/api/PluginApiClient.ts +++ b/plugins/github-release-manager/src/api/PluginApiClient.ts @@ -18,18 +18,10 @@ import { ConfigApi, OAuthApi } from '@backstage/core'; import { Octokit } from '@octokit/rest'; import { readGitHubIntegrationConfigs } from '@backstage/integration'; -import { - GhCreateCommitResponse, - GhCreateReferenceResponse, - GhCreateTagObjectResponse, - GhGetReleaseResponse, - GhUpdateReferenceResponse, - GhUpdateReleaseResponse, -} from '../types/types'; import { CalverTagParts } from '../helpers/tagParts/getCalverTagParts'; import { getRcGitHubInfo } from '../cards/createRc/getRcGitHubInfo'; -import { SemverTagParts } from '../helpers/tagParts/getSemverTagParts'; import { Project } from '../contexts/ProjectContext'; +import { SemverTagParts } from '../helpers/tagParts/getSemverTagParts'; type UnboxPromise> = T extends Promise ? U @@ -41,7 +33,6 @@ export type ApiMethodRetval< export type UnboxArray = T extends (infer U)[] ? U : T; -type Todo = any; // TODO: type PartialProject = Omit; export interface IPluginApiClient { @@ -205,29 +196,48 @@ export interface IPluginApiClient { mergeTree: string; releaseBranchSha: string; } & PartialProject, - ) => Promise; + ) => Promise<{ + message: string; + sha: string; + }>; replaceTempCommit: ( args: { releaseBranchName: string; - cherryPickCommit: GhCreateCommitResponse; + cherryPickCommit: ApiMethodRetval< + IPluginApiClient['patch']['createCherryPickCommit'] + >; } & PartialProject, - ) => Promise; + ) => Promise<{ + ref: string; + object: { + sha: string; + }; + }>; createTagObject: ({ bumpedTag, updatedReference, }: { bumpedTag: string; - updatedReference: GhUpdateReferenceResponse; - } & PartialProject) => Promise; + updatedReference: ApiMethodRetval< + IPluginApiClient['patch']['replaceTempCommit'] + >; + } & PartialProject) => Promise<{ + tag: string; + sha: string; + }>; createReference: ( args: { bumpedTag: string; - tagObjectResponse: GhCreateTagObjectResponse; + createdTagObject: ApiMethodRetval< + IPluginApiClient['patch']['createTagObject'] + >; } & PartialProject, - ) => Promise; + ) => Promise<{ + ref: string; + }>; updateRelease: ( args: { @@ -240,16 +250,26 @@ export interface IPluginApiClient { ApiMethodRetval['recentCommits'] >; } & PartialProject, - ) => Promise; + ) => Promise<{ + name: string | null; + tagName: string; + htmlUrl: string; + }>; }; promoteRc: { promoteRelease: ( args: { - releaseId: GhGetReleaseResponse['id']; + releaseId: NonNullable< + ApiMethodRetval['latestRelease'] + >['id']; releaseVersion: string; } & PartialProject, - ) => Promise; + ) => Promise<{ + name: string | null; + tagName: string; + htmlUrl: string; + }>; }; } @@ -645,22 +665,18 @@ export class PluginApiClient implements IPluginApiClient { releaseBranchSha: string; } & PartialProject) => { const { octokit } = await this.getOctokit(); + const { data: cherryPickCommit } = await octokit.git.createCommit({ + owner, + repo, + message: `[patch ${bumpedTag}] ${selectedPatchCommit.commit.message}`, + tree: mergeTree, + parents: [releaseBranchSha], + }); - const cherryPickCommit: GhCreateCommitResponse = ( - await octokit.request( - `/repos/${this.getRepoPath({ owner, repo })}/git/commits`, - { - method: 'POST', - data: { - message: `[patch ${bumpedTag}] ${selectedPatchCommit.commit.message}`, - tree: mergeTree, - parents: [releaseBranchSha], - }, - }, - ) - ).data; - - return { cherryPickCommit }; + return { + message: cherryPickCommit.message, + sha: cherryPickCommit.sha, + }; }, replaceTempCommit: async ({ @@ -670,27 +686,25 @@ export class PluginApiClient implements IPluginApiClient { cherryPickCommit, }: { releaseBranchName: string; - cherryPickCommit: GhCreateCommitResponse; + cherryPickCommit: ApiMethodRetval< + IPluginApiClient['patch']['createCherryPickCommit'] + >; } & PartialProject) => { const { octokit } = await this.getOctokit(); + const { data: updatedReference } = await octokit.git.updateRef({ + owner, + repo, + ref: releaseBranchName, + sha: cherryPickCommit.sha, + force: true, + }); - const updatedReference: GhUpdateReferenceResponse = ( - await octokit.request( - `/repos/${this.getRepoPath({ - owner, - repo, - })}/git/refs/heads/${releaseBranchName}`, - { - method: 'PATCH', - data: { - sha: cherryPickCommit.sha, - force: true, - }, - }, - ) - ).data; - - return { updatedReference }; + return { + ref: updatedReference.ref, + object: { + sha: updatedReference.object.sha, + }, + }; }, createTagObject: async ({ @@ -700,54 +714,49 @@ export class PluginApiClient implements IPluginApiClient { updatedReference, }: { bumpedTag: string; - updatedReference: GhUpdateReferenceResponse; + updatedReference: ApiMethodRetval< + IPluginApiClient['patch']['replaceTempCommit'] + >; } & PartialProject) => { const { octokit } = await this.getOctokit(); + const { data: createdTagObject } = await octokit.git.createTag({ + owner, + repo, + message: + 'Tag generated by your friendly neighborhood Backstage Release Manager', + tag: bumpedTag, + object: updatedReference.object.sha, + type: 'commit', + }); - const tagObjectResponse: GhCreateTagObjectResponse = ( - await octokit.request( - `/repos/${this.getRepoPath({ owner, repo })}/git/tags`, - { - method: 'POST', - data: { - type: 'commit', - message: - 'Tag generated by your friendly neighborhood GitHub Release Manager', - tag: bumpedTag, - object: updatedReference.object.sha, - }, - }, - ) - ).data; - - return { tagObjectResponse }; + return { + tag: createdTagObject.tag, + sha: createdTagObject.sha, + }; }, createReference: async ({ owner, repo, bumpedTag, - tagObjectResponse, + createdTagObject, }: { bumpedTag: string; - tagObjectResponse: GhCreateTagObjectResponse; + createdTagObject: ApiMethodRetval< + IPluginApiClient['patch']['createTagObject'] + >; } & PartialProject) => { const { octokit } = await this.getOctokit(); + const { data: reference } = await octokit.git.createRef({ + owner, + repo, + ref: `refs/tags/${bumpedTag}`, + sha: createdTagObject.sha, + }); - const reference: GhCreateReferenceResponse = ( - await octokit.request( - `/repos/${this.getRepoPath({ owner, repo })}/git/refs`, - { - method: 'POST', - data: { - ref: `refs/tags/${bumpedTag}`, - sha: tagObjectResponse.sha, - }, - }, - ) - ).data; - - return { reference }; + return { + ref: reference.ref, + }; }, updateRelease: async ({ @@ -768,27 +777,23 @@ export class PluginApiClient implements IPluginApiClient { >; } & PartialProject) => { const { octokit } = await this.getOctokit(); + const { data: updatedRelease } = await octokit.repos.updateRelease({ + owner, + repo, + release_id: latestRelease.id, + tag_name: bumpedTag, + body: `${latestRelease.body} - const release: GhUpdateReleaseResponse = ( - await octokit.request( - `/repos/${this.getRepoPath({ owner, repo })}/releases/${ - latestRelease.id - }`, - { - method: 'PATCH', - data: { - tag_name: bumpedTag, - body: `${latestRelease.body} + #### [Patch ${tagParts.patch}](${selectedPatchCommit.htmlUrl}) - #### [Patch ${tagParts.patch}](${selectedPatchCommit.htmlUrl}) - - ${selectedPatchCommit.commit.message}`, - }, - }, - ) - ).data; + ${selectedPatchCommit.commit.message}`, + }); - return { release }; + return { + name: updatedRelease.name, + tagName: updatedRelease.tag_name, + htmlUrl: updatedRelease.html_url, + }; }, }; @@ -799,25 +804,25 @@ export class PluginApiClient implements IPluginApiClient { releaseId, releaseVersion, }: { - releaseId: GhGetReleaseResponse['id']; + releaseId: NonNullable< + ApiMethodRetval['latestRelease'] + >['id']; releaseVersion: string; } & PartialProject) => { const { octokit } = await this.getOctokit(); + const { data: promotedRelease } = await octokit.repos.updateRelease({ + owner, + repo, + release_id: releaseId, + tag_name: releaseVersion, + prerelease: false, + }); - const release: GhGetReleaseResponse = ( - await octokit.request( - `/repos/${this.getRepoPath({ owner, repo })}/releases/${releaseId}`, - { - method: 'PATCH', - data: { - tag_name: releaseVersion, - prerelease: false, - }, - }, - ) - ).data; - - return { release }; + return { + name: promotedRelease.name, + tagName: promotedRelease.tag_name, + htmlUrl: promotedRelease.html_url, + }; }, }; } diff --git a/plugins/github-release-manager/src/cards/createRc/CreateRc.tsx b/plugins/github-release-manager/src/cards/createRc/CreateRc.tsx index 0bc0e91297..d629e9beeb 100644 --- a/plugins/github-release-manager/src/cards/createRc/CreateRc.tsx +++ b/plugins/github-release-manager/src/cards/createRc/CreateRc.tsx @@ -30,11 +30,7 @@ import { createRc } from './sideEffects/createRc'; import { Differ } from '../../components/Differ'; import { getRcGitHubInfo } from './getRcGitHubInfo'; import { InfoCardPlus } from '../../components/InfoCardPlus'; -import { - ComponentConfigCreateRc, - GhGetRepositoryResponse, - SetRefetch, -} from '../../types/types'; +import { ComponentConfigCreateRc, SetRefetch } from '../../types/types'; import { ResponseStepList } from '../../components/ResponseStepList/ResponseStepList'; import { SEMVER_PARTS } from '../../constants/constants'; import { TEST_IDS } from '../../test-helpers/test-ids'; @@ -44,7 +40,9 @@ import { useStyles } from '../../styles/styles'; import { ApiMethodRetval, IPluginApiClient } from '../../api/PluginApiClient'; interface CreateRcProps { - defaultBranch: GhGetRepositoryResponse['default_branch']; + defaultBranch: ApiMethodRetval< + IPluginApiClient['getRepository'] + >['repository']['defaultBranch']; latestRelease: ApiMethodRetval< IPluginApiClient['getLatestRelease'] >['latestRelease']; diff --git a/plugins/github-release-manager/src/cards/createRc/sideEffects/createRc.ts b/plugins/github-release-manager/src/cards/createRc/sideEffects/createRc.ts index 378fa22a82..b7b37bd143 100644 --- a/plugins/github-release-manager/src/cards/createRc/sideEffects/createRc.ts +++ b/plugins/github-release-manager/src/cards/createRc/sideEffects/createRc.ts @@ -15,11 +15,7 @@ */ import { getRcGitHubInfo } from '../getRcGitHubInfo'; -import { - ComponentConfigCreateRc, - GhGetRepositoryResponse, - ResponseStep, -} from '../../../types/types'; +import { ComponentConfigCreateRc, ResponseStep } from '../../../types/types'; import { ApiMethodRetval, IPluginApiClient, @@ -28,7 +24,9 @@ import { GitHubReleaseManagerError } from '../../../errors/GitHubReleaseManagerE import { Project } from '../../../contexts/ProjectContext'; interface CreateRC { - defaultBranch: GhGetRepositoryResponse['default_branch']; + defaultBranch: ApiMethodRetval< + IPluginApiClient['getRepository'] + >['repository']['defaultBranch']; latestRelease: ApiMethodRetval< IPluginApiClient['getLatestRelease'] >['latestRelease']; diff --git a/plugins/github-release-manager/src/cards/patchRc/sideEffects/patch.test.ts b/plugins/github-release-manager/src/cards/patchRc/sideEffects/patch.test.ts index 9d00824b3b..6ba5dfb2c9 100644 --- a/plugins/github-release-manager/src/cards/patchRc/sideEffects/patch.test.ts +++ b/plugins/github-release-manager/src/cards/patchRc/sideEffects/patch.test.ts @@ -54,7 +54,7 @@ describe('patch', () => { }, Object { "message": "Cherry-picked patch commit to \\"mock_branch_commit_sha\\"", - "secondaryMessage": "with message \\"undefined\\"", + "secondaryMessage": "with message \\"mock_cherrypick_message\\"", }, Object { "message": "Updated reference \\"mock_reference_ref\\"", diff --git a/plugins/github-release-manager/src/cards/patchRc/sideEffects/patch.ts b/plugins/github-release-manager/src/cards/patchRc/sideEffects/patch.ts index 13c2bbb671..bce13d64b4 100644 --- a/plugins/github-release-manager/src/cards/patchRc/sideEffects/patch.ts +++ b/plugins/github-release-manager/src/cards/patchRc/sideEffects/patch.ts @@ -125,9 +125,7 @@ export async function patch({ * Note that branchSha is the original from up at the top. * > cherry = POST /repos/$owner/$repo/git/commits { "message": "looks good!", "tree": mergeTree, "parents": [branchSha] } */ - const { - cherryPickCommit, - } = await pluginApiClient.patch.createCherryPickCommit({ + const cherryPickCommit = await pluginApiClient.patch.createCherryPickCommit({ ...project, bumpedTag, mergeTree, @@ -143,7 +141,7 @@ export async function patch({ * 6. Replace the temp commit with the real commit: * > PATCH /repos/$owner/$repo/git/refs/heads/$refName { sha = cherry.sha, force = true } */ - const { updatedReference } = await pluginApiClient.patch.replaceTempCommit({ + const updatedReference = await pluginApiClient.patch.replaceTempCommit({ ...project, cherryPickCommit, releaseBranchName, @@ -156,53 +154,51 @@ export async function patch({ * 7. Create tag object: https://developer.github.com/v3/git/tags/#create-a-tag-object * > POST /repos/:owner/:repo/git/tags */ - const { tagObjectResponse } = await pluginApiClient.patch.createTagObject({ + const createdTagObject = await pluginApiClient.patch.createTagObject({ ...project, bumpedTag, updatedReference, }); responseSteps.push({ message: 'Created new tag object', - secondaryMessage: `with name "${tagObjectResponse.tag}"`, + secondaryMessage: `with name "${createdTagObject.tag}"`, }); /** * 8. Create a reference: https://developer.github.com/v3/git/refs/#create-a-reference * > POST /repos/:owner/:repo/git/refs */ - const { reference } = await pluginApiClient.patch.createReference({ + const reference = await pluginApiClient.patch.createReference({ ...project, bumpedTag, - tagObjectResponse, + createdTagObject, }); responseSteps.push({ message: `Created new reference "${reference.ref}"`, - secondaryMessage: `for tag object "${tagObjectResponse.tag}"`, + secondaryMessage: `for tag object "${createdTagObject.tag}"`, }); /** * 9. Update release */ - const { release: updatedRelease } = await pluginApiClient.patch.updateRelease( - { - ...project, - bumpedTag, - latestRelease, - selectedPatchCommit, - tagParts, - }, - ); + const updatedRelease = await pluginApiClient.patch.updateRelease({ + ...project, + bumpedTag, + latestRelease, + selectedPatchCommit, + tagParts, + }); responseSteps.push({ message: `Updated release "${updatedRelease.name}"`, - secondaryMessage: `with tag ${updatedRelease.tag_name}`, - link: updatedRelease.html_url, + secondaryMessage: `with tag ${updatedRelease.tagName}`, + link: updatedRelease.htmlUrl, }); await successCb?.({ - updatedReleaseUrl: updatedRelease.html_url, + updatedReleaseUrl: updatedRelease.htmlUrl, updatedReleaseName: updatedRelease.name, previousTag: latestRelease.tagName, - patchedTag: updatedRelease.tag_name, + patchedTag: updatedRelease.tagName, patchCommitUrl: selectedPatchCommit.htmlUrl, patchCommitMessage: selectedPatchCommit.commit.message, }); diff --git a/plugins/github-release-manager/src/cards/promoteRc/sideEffects/promoteRc.ts b/plugins/github-release-manager/src/cards/promoteRc/sideEffects/promoteRc.ts index 4a254ca2a9..42a5108267 100644 --- a/plugins/github-release-manager/src/cards/promoteRc/sideEffects/promoteRc.ts +++ b/plugins/github-release-manager/src/cards/promoteRc/sideEffects/promoteRc.ts @@ -41,24 +41,24 @@ export function promoteRc({ return async (): Promise => { const responseSteps: ResponseStep[] = []; - const { release } = await pluginApiClient.promoteRc.promoteRelease({ + const promotedRelease = await pluginApiClient.promoteRc.promoteRelease({ ...project, releaseId: rcRelease.id, releaseVersion, }); responseSteps.push({ - message: `Promoted "${release.name}"`, - secondaryMessage: `from "${rcRelease.tagName}" to "${release.tag_name}"`, - link: release.html_url, + message: `Promoted "${promotedRelease.name}"`, + secondaryMessage: `from "${rcRelease.tagName}" to "${promotedRelease.tagName}"`, + link: promotedRelease.htmlUrl, }); await successCb?.({ - gitHubReleaseUrl: release.html_url, - gitHubReleaseName: release.name, + gitHubReleaseUrl: promotedRelease.htmlUrl, + gitHubReleaseName: promotedRelease.name, previousTagUrl: rcRelease.htmlUrl, previousTag: rcRelease.tagName, - updatedTagUrl: release.html_url, - updatedTag: release.tag_name, + updatedTagUrl: promotedRelease.htmlUrl, + updatedTag: promotedRelease.tagName, }); return responseSteps; diff --git a/plugins/github-release-manager/src/test-helpers/test-helpers.ts b/plugins/github-release-manager/src/test-helpers/test-helpers.ts index 0bc1246b8a..cb3fdfb1b1 100644 --- a/plugins/github-release-manager/src/test-helpers/test-helpers.ts +++ b/plugins/github-release-manager/src/test-helpers/test-helpers.ts @@ -16,14 +16,6 @@ import { CalverTagParts } from '../helpers/tagParts/getCalverTagParts'; import { getRcGitHubInfo } from '../cards/createRc/getRcGitHubInfo'; -import { - GhCreateReferenceResponse, - GhCreateTagObjectResponse, - GhGetReleaseResponse, - GhMergeResponse, - GhUpdateReferenceResponse, - GhUpdateReleaseResponse, -} from '../types/types'; import { Project } from '../contexts/ProjectContext'; import { ApiMethodRetval, @@ -179,6 +171,7 @@ export const mockApiClient: IPluginApiClient = { createRef: jest.fn().mockResolvedValue({ ref: 'mock_createRef_ref', } as NonNullable>), + createRelease: jest.fn().mockResolvedValue({ createReleaseResponse: { name: 'mock_createRelease_name', @@ -186,37 +179,34 @@ export const mockApiClient: IPluginApiClient = { tagName: 'mock_createRelease_tag_name', }, } as NonNullable>), + getComparison: jest.fn().mockResolvedValue({ htmlUrl: 'mock_compareCommits_html_url', aheadBy: 1, } as NonNullable>), }, + patch: { createCherryPickCommit: jest.fn().mockResolvedValue({ - cherryPickCommit: { - commit: { - message: 'mock_merge_commit_message', - tree: { sha: 'mock_merge_commit_tree_sha' }, - }, - html_url: 'mock_merge_html_url', - } as GhMergeResponse, - }), + message: 'mock_cherrypick_message', + sha: 'mock_cherrypick_sha', + } as NonNullable>), + createReference: jest.fn().mockResolvedValue({ - reference: { - ref: 'mock_reference_ref', - } as GhCreateReferenceResponse, - }), + ref: 'mock_reference_ref', + } as ApiMethodRetval), + createTagObject: jest.fn().mockResolvedValue({ - tagObjectResponse: { - tag: 'mock_tag_object_tag', - sha: 'mock_tag_object_sha', - } as GhCreateTagObjectResponse, - }), + tag: 'mock_tag_object_tag', + sha: 'mock_tag_object_sha', + } as ApiMethodRetval), + createTempCommit: jest.fn().mockResolvedValue({ message: 'mock_commit_message', sha: 'mock_commit_sha', } as ApiMethodRetval), forceBranchHeadToTempCommit: jest.fn().mockResolvedValue(undefined), + merge: jest.fn().mockResolvedValue({ htmlUrl: 'mock_merge_html_url', commit: { @@ -226,28 +216,26 @@ export const mockApiClient: IPluginApiClient = { }, }, } as ApiMethodRetval), + replaceTempCommit: jest.fn().mockResolvedValue({ - updatedReference: { - ref: 'mock_reference_ref', - object: { sha: 'mock_reference_object_sha' }, - } as GhUpdateReferenceResponse, - }), + ref: 'mock_reference_ref', + object: { + sha: 'mock_reference_object_sha', + }, + } as ApiMethodRetval), + updateRelease: jest.fn().mockResolvedValue({ - release: { - name: 'mock_update_release_name', - tag_name: 'mock_update_release_tag_name', - html_url: 'mock_update_release_html_url', - } as GhUpdateReleaseResponse, - }), + name: 'mock_update_release_name', + tagName: 'mock_update_release_tag_name', + htmlUrl: 'mock_update_release_html_url', + } as ApiMethodRetval), }, + promoteRc: { promoteRelease: jest.fn().mockResolvedValue({ - release: { - name: 'mock_release_name', - tag_name: 'mock_release_tag_name', - html_url: 'mock_release_html_url', - } as GhGetReleaseResponse, - }), + name: 'mock_release_name', + tagName: 'mock_release_tag_name', + htmlUrl: 'mock_release_html_url', + } as ApiMethodRetval), }, - // project: mockSemverProject, }; diff --git a/plugins/github-release-manager/src/types/types.ts b/plugins/github-release-manager/src/types/types.ts index 8853efc71b..ebdc941c12 100644 --- a/plugins/github-release-manager/src/types/types.ts +++ b/plugins/github-release-manager/src/types/types.ts @@ -40,7 +40,7 @@ export type ComponentConfigPromoteRc = ComponentConfig