diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index c730f0a7046430b4d5c8e034f82c261f15bd7141..daa7ab52662cfe974cf2356e257f1032fa96453c 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -255,6 +255,18 @@ Add config here if you wish it to apply to Docker package managers Dockerfile an ## dotnet +## draftPR + +If you want the PRs created by Renovate to be considered as drafts rather than normal PRs in Github you could add this property to your `renovate.json`: + +```json +{ + "draftPR": true +} +``` + +Please see [draft pull requests on Github](https://github.blog/2019-02-14-introducing-draft-pull-requests/) for more information about draft PRs. + ## enabled The most common use of `enabled` is if you want to turn Renovate's functionality off, for some reason. diff --git a/lib/config/common.ts b/lib/config/common.ts index dadcb71fb15f4e77f9c8256912ab070d2ae395fa..f212f835574eb190ddf35b7560ddf1e63305c2e0 100644 --- a/lib/config/common.ts +++ b/lib/config/common.ts @@ -21,6 +21,7 @@ export interface RenovateSharedConfig { branchName?: string; manager?: string; commitMessage?: string; + draftPR?: boolean; enabled?: boolean; enabledManagers?: string[]; fileMatch?: string[]; diff --git a/lib/config/definitions.ts b/lib/config/definitions.ts index e0c67b44aa2ce9cd15a82e1f53d4573ec91d8c46..1bd317846885a189005d3d09c78cdc023e2b6929 100644 --- a/lib/config/definitions.ts +++ b/lib/config/definitions.ts @@ -216,6 +216,12 @@ const options: RenovateOptions[] = [ type: 'boolean', default: true, }, + { + name: 'draftPR', + description: 'If enabled, the PR created by Renovate is set to a draft.', + type: 'boolean', + default: false, + }, { name: 'dryRun', description: diff --git a/lib/platform/common.ts b/lib/platform/common.ts index 7accae09dbfdd1e7de78bb03b8e5637563d46b99..79049df5fe29785e03e79dac04d244471ff0d088 100644 --- a/lib/platform/common.ts +++ b/lib/platform/common.ts @@ -139,6 +139,7 @@ export interface CreatePRConfig { labels?: string[] | null; useDefaultBranch?: boolean; platformOptions?: PlatformPrOptions; + draftPR?: boolean; } export interface EnsureIssueConfig { title: string; diff --git a/lib/platform/github/__snapshots__/index.spec.ts.snap b/lib/platform/github/__snapshots__/index.spec.ts.snap index 47717b36c70982a8c5d9167728bb2f0d72c031bd..c34f8fcac2258a86c8dd4fecca23b6a91f0efe71 100644 --- a/lib/platform/github/__snapshots__/index.spec.ts.snap +++ b/lib/platform/github/__snapshots__/index.spec.ts.snap @@ -60,6 +60,45 @@ Array [ ] `; +exports[`platform/github createPr() should create a draftPR if set in the settings 1`] = ` +Object { + "branchName": "some-branch", + "displayNumber": "Pull Request #123", + "isModified": false, + "number": 123, +} +`; + +exports[`platform/github createPr() should create a draftPR if set in the settings 2`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc123", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/some/repo", + }, + Object { + "body": "{\\"title\\":\\"PR draft\\",\\"head\\":\\"some:some-branch\\",\\"base\\":\\"master\\",\\"body\\":\\"This is a result of a draft\\",\\"draft\\":true}", + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc123", + "content-length": 112, + "content-type": "application/json", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "POST", + "url": "https://api.github.com/repos/some/repo/pulls", + }, +] +`; + exports[`platform/github createPr() should create and return a PR object 1`] = ` Object { "branchName": "some-branch", @@ -83,12 +122,12 @@ Array [ "url": "https://api.github.com/repos/some/repo", }, Object { - "body": "{\\"title\\":\\"The Title\\",\\"head\\":\\"some:some-branch\\",\\"base\\":\\"master\\",\\"body\\":\\"Hello world\\"}", + "body": "{\\"title\\":\\"The Title\\",\\"head\\":\\"some:some-branch\\",\\"base\\":\\"master\\",\\"body\\":\\"Hello world\\",\\"draft\\":false}", "headers": Object { "accept": "application/json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 84, + "content-length": 98, "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", @@ -183,12 +222,12 @@ Array [ "url": "https://api.github.com/repos/some/repo", }, Object { - "body": "{\\"title\\":\\"The Title\\",\\"head\\":\\"some:some-branch\\",\\"base\\":\\"master\\",\\"body\\":\\"Hello world\\"}", + "body": "{\\"title\\":\\"The Title\\",\\"head\\":\\"some:some-branch\\",\\"base\\":\\"master\\",\\"body\\":\\"Hello world\\",\\"draft\\":false}", "headers": Object { "accept": "application/json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 84, + "content-length": 98, "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", diff --git a/lib/platform/github/index.spec.ts b/lib/platform/github/index.spec.ts index 13925d7eb01892407cf9e4b0de1e0c0de53e1631..567eaadfe7a7d8eb6ee01435b43fb7aa6560b7c8 100644 --- a/lib/platform/github/index.spec.ts +++ b/lib/platform/github/index.spec.ts @@ -1577,6 +1577,24 @@ describe('platform/github', () => { expect(pr).toMatchSnapshot(); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('should create a draftPR if set in the settings', async () => { + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + scope.post('/repos/some/repo/pulls').reply(200, { + number: 123, + }); + await github.initRepo({ repository: 'some/repo', token: 'token' } as any); + const pr = await github.createPr({ + branchName: 'some-branch', + prTitle: 'PR draft', + prBody: 'This is a result of a draft', + labels: null, + useDefaultBranch: true, + draftPR: true, + }); + expect(pr).toMatchSnapshot(); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); }); describe('getPr(prNo)', () => { it('should return null if no prNo is passed', async () => { diff --git a/lib/platform/github/index.ts b/lib/platform/github/index.ts index 07731c48479b974176527c925043e9af457b2a12..f2017cbddb731c05dbf70a5e6e564f1541ebe8a5 100644 --- a/lib/platform/github/index.ts +++ b/lib/platform/github/index.ts @@ -1619,6 +1619,7 @@ export async function createPr({ labels, useDefaultBranch, platformOptions = {}, + draftPR = false, }: CreatePRConfig): Promise<Pr> { const body = sanitize(rawBody); const base = useDefaultBranch ? config.defaultBranch : config.baseBranch; @@ -1630,6 +1631,7 @@ export async function createPr({ head, base, body, + draft: draftPR, }, }; // istanbul ignore if @@ -1637,14 +1639,17 @@ export async function createPr({ options.token = config.forkToken; options.body.maintainer_can_modify = true; } - logger.debug({ title, head, base }, 'Creating PR'); + logger.debug({ title, head, base, draft: draftPR }, 'Creating PR'); const pr = ( await githubApi.postJson<GhPr>( `repos/${config.parentRepo || config.repository}/pulls`, options ) ).body; - logger.debug({ branch: branchName, pr: pr.number }, 'PR created'); + logger.debug( + { branch: branchName, pr: pr.number, draft: draftPR }, + 'PR created' + ); // istanbul ignore if if (config.prList) { config.prList.push(pr); diff --git a/lib/workers/pr/__snapshots__/index.spec.ts.snap b/lib/workers/pr/__snapshots__/index.spec.ts.snap index e4ec27af3cded026927867b2cfb2132191738a57..f94acbf5a45abaf7588b605c5ca1695bd43f6570 100644 --- a/lib/workers/pr/__snapshots__/index.spec.ts.snap +++ b/lib/workers/pr/__snapshots__/index.spec.ts.snap @@ -42,6 +42,7 @@ exports[`workers/pr ensurePr should add note about Pin 1`] = ` Array [ Object { "branchName": "renovate/dummy-1.x", + "draftPR": false, "labels": Array [], "platformOptions": Object { "azureAutoComplete": false, @@ -72,6 +73,7 @@ exports[`workers/pr ensurePr should create PR if success 1`] = ` Array [ Object { "branchName": "renovate/dummy-1.x", + "draftPR": false, "labels": Array [], "platformOptions": Object { "azureAutoComplete": false, @@ -89,6 +91,7 @@ exports[`workers/pr ensurePr should create PR if success for gitlab deps 1`] = ` Array [ Object { "branchName": "renovate/gitlabdummy-1.x", + "draftPR": false, "labels": Array [], "platformOptions": Object { "azureAutoComplete": false, @@ -106,6 +109,7 @@ exports[`workers/pr ensurePr should create group PR 1`] = ` Array [ Object { "branchName": "renovate/dummy-1.x", + "draftPR": false, "labels": Array [], "platformOptions": Object { "azureAutoComplete": false, @@ -123,6 +127,7 @@ exports[`workers/pr ensurePr should create privateRepo PR if success 1`] = ` Array [ Object { "branchName": "renovate/dummy-1.x", + "draftPR": false, "labels": Array [], "platformOptions": Object { "azureAutoComplete": false, diff --git a/lib/workers/pr/index.ts b/lib/workers/pr/index.ts index db7659a6037c97d55fdcf7b00c0aa84322c0726d..5aaef0796dade061b6722e639f59f215a686f740 100644 --- a/lib/workers/pr/index.ts +++ b/lib/workers/pr/index.ts @@ -354,6 +354,7 @@ export async function ensurePr( labels: config.labels, useDefaultBranch: false, platformOptions, + draftPR: config.draftPR, }); logger.info({ pr: pr.number, prTitle }, 'PR created'); }