diff --git a/.changeset/curly-parrots-applaud.md b/.changeset/curly-parrots-applaud.md new file mode 100644 index 0000000000..d9443415eb --- /dev/null +++ b/.changeset/curly-parrots-applaud.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Update the `github:publish` action to allow passing required status check +contexts before merging to the main branch. diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index ff00ed5f0b..1bc8a27f54 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -266,6 +266,7 @@ export function createPublishGithubAction(options: { allowMergeCommit?: boolean | undefined; sourcePath?: string | undefined; requireCodeOwnerReviews?: boolean | undefined; + requiredStatusCheckContexts?: string[] | undefined; repoVisibility?: 'internal' | 'private' | 'public' | undefined; collaborators?: | { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts index 75e22522d5..529a504cba 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts @@ -132,6 +132,7 @@ type BranchProtectionOptions = { repoName: string; logger: Logger; requireCodeOwnerReviews: boolean; + requiredStatusCheckContexts?: string[]; defaultBranch?: string; }; @@ -141,6 +142,7 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({ owner, logger, requireCodeOwnerReviews, + requiredStatusCheckContexts = [], defaultBranch = 'master', }: BranchProtectionOptions): Promise => { const tryOnce = async () => { @@ -159,7 +161,10 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({ owner, repo: repoName, branch: defaultBranch, - required_status_checks: { strict: true, contexts: [] }, + required_status_checks: { + strict: true, + contexts: requiredStatusCheckContexts, + }, restrictions: null, enforce_admins: true, required_pull_request_reviews: { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts index 0af177ce4d..f1a6c9bfe8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts @@ -628,6 +628,7 @@ describe('publish:github', () => { logger: mockContext.logger, defaultBranch: 'master', requireCodeOwnerReviews: false, + requiredStatusCheckContexts: [], }); await action.handler({ @@ -645,6 +646,7 @@ describe('publish:github', () => { logger: mockContext.logger, defaultBranch: 'master', requireCodeOwnerReviews: true, + requiredStatusCheckContexts: [], }); await action.handler({ @@ -662,6 +664,67 @@ describe('publish:github', () => { logger: mockContext.logger, defaultBranch: 'master', requireCodeOwnerReviews: false, + requiredStatusCheckContexts: [], + }); + }); + + it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of requiredStatusCheckContexts', async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'User' }, + }); + + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + data: { + name: 'repository', + }, + }); + + await action.handler(mockContext); + + expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ + owner: 'owner', + client: mockOctokit, + repoName: 'repository', + logger: mockContext.logger, + defaultBranch: 'master', + requireCodeOwnerReviews: false, + requiredStatusCheckContexts: [], + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + requiredStatusCheckContexts: ['statusCheck'], + }, + }); + + expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ + owner: 'owner', + client: mockOctokit, + repoName: 'repository', + logger: mockContext.logger, + defaultBranch: 'master', + requireCodeOwnerReviews: false, + requiredStatusCheckContexts: ['statusCheck'], + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + requiredStatusCheckContexts: [], + }, + }); + + expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ + owner: 'owner', + client: mockOctokit, + repoName: 'repository', + logger: mockContext.logger, + defaultBranch: 'master', + requireCodeOwnerReviews: false, + requiredStatusCheckContexts: [], }); }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts index c80f76b4e7..340eb06dcd 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts @@ -55,6 +55,7 @@ export function createPublishGithubAction(options: { allowMergeCommit?: boolean; sourcePath?: string; requireCodeOwnerReviews?: boolean; + requiredStatusCheckContexts?: string[]; repoVisibility?: 'private' | 'internal' | 'public'; collaborators?: Array<{ username: string; @@ -91,6 +92,15 @@ export function createPublishGithubAction(options: { 'Require an approved review in PR including files with a designated Code Owner', type: 'boolean', }, + requiredStatusCheckContexts: { + title: 'Required Status Check Contexts', + description: + 'The list of status checks to require in order to merge into this branch', + type: 'array', + items: { + type: 'string', + }, + }, repoVisibility: { title: 'Repository Visibility', type: 'string', @@ -196,6 +206,7 @@ export function createPublishGithubAction(options: { description, access, requireCodeOwnerReviews = false, + requiredStatusCheckContexts = [], repoVisibility = 'private', defaultBranch = 'master', deleteBranchOnMerge = false, @@ -342,6 +353,7 @@ export function createPublishGithubAction(options: { logger: ctx.logger, defaultBranch, requireCodeOwnerReviews, + requiredStatusCheckContexts, }); } catch (e) { assertError(e);