diff --git a/lib/config/definitions.js b/lib/config/definitions.js index ba5c51df3ea0dda6198efdfdfd7afa0b78eff4be..86cae8395947ed6a1b1799110c53bc9691612685 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -209,6 +209,14 @@ const options = [ type: 'string', default: `Update Dependencies (${appName} Bot)`, }, + { + name: 'configWarningReuseIssue', + description: + 'Set this to false and Renovate will open each config warning in a new issue instead of reopening/reusing an existing issue.', + type: 'boolean', + default: true, + }, + // encryption { name: 'privateKey', diff --git a/lib/platform/github/index.ts b/lib/platform/github/index.ts index 367b5bc39c53c9219bfefdcb62c1c19464aa1bf2..48fa7fe937494ac4a315811df90e0210e3f819e0 100644 --- a/lib/platform/github/index.ts +++ b/lib/platform/github/index.ts @@ -820,7 +820,12 @@ export async function findIssue(title: string) { }; } -export async function ensureIssue(title: string, body: string, once = false) { +export async function ensureIssue( + title: string, + body: string, + once = false, + reopen = true +) { logger.debug(`ensureIssue()`); try { const issueList = await getIssueList(); @@ -832,7 +837,9 @@ export async function ensureIssue(title: string, body: string, once = false) { logger.debug('Issue already closed - skipping recreation'); return null; } - logger.info('Reopening previously closed issue'); + if (reopen) { + logger.info('Reopening previously closed issue'); + } issue = issues[issues.length - 1]; } for (const i of issues) { @@ -848,17 +855,19 @@ export async function ensureIssue(title: string, body: string, once = false) { logger.info('Issue is open and up to date - nothing to do'); return null; } - logger.info('Patching issue'); - await api.patch( - `repos/${config.parentRepo || config.repository}/issues/${ - issue.number - }`, - { - body: { body, state: 'open' }, - } - ); - logger.info('Issue updated'); - return 'updated'; + if (reopen) { + logger.info('Patching issue'); + await api.patch( + `repos/${config.parentRepo || config.repository}/issues/${ + issue.number + }`, + { + body: { body, state: 'open' }, + } + ); + logger.info('Issue updated'); + return 'updated'; + } } await api.post(`repos/${config.parentRepo || config.repository}/issues`, { body: { diff --git a/lib/workers/repository/error-config.js b/lib/workers/repository/error-config.js index 11ac39443d6901045aec905eb02316a1904e3ba6..0813b27f251a91087d1bbb4f2bf03ca5bec8cfaf 100644 --- a/lib/workers/repository/error-config.js +++ b/lib/workers/repository/error-config.js @@ -30,9 +30,13 @@ async function raiseConfigWarningIssue(config, error) { } else if (config.dryRun) { logger.info('DRY-RUN: Would ensure config error issue'); } else { + const once = false; + const shouldReopen = config.configWarningReuseIssue; const res = await platform.ensureIssue( `Action Required: Fix ${appName} Configuration`, - body + body, + once, + shouldReopen ); if (res === 'created') { logger.warn({ configError: error, res }, 'Config Warning'); diff --git a/renovate-schema.json b/renovate-schema.json index 05b0e7cb293908af42f60eb7058665e945ddeac2..227a18e85133e1458269bebb08d1bf9e3d47eb80 100644 --- a/renovate-schema.json +++ b/renovate-schema.json @@ -131,6 +131,11 @@ "type": "string", "default": "Update Dependencies (Renovate Bot)" }, + "configWarningReuseIssue": { + "description": "Set this to false and Renovate will open each config warning in a new issue instead of reopening/reusing an existing issue.", + "type": "boolean", + "default": true + }, "privateKey": { "description": "Server-side private key", "type": "string" diff --git a/test/platform/github/index.spec.ts b/test/platform/github/index.spec.ts index 92e886f0d2a6aa06dc758f916429b673a8a90910..555a6438886cafe4a7c6d9f2f268bb4a780edc49 100644 --- a/test/platform/github/index.spec.ts +++ b/test/platform/github/index.spec.ts @@ -1175,6 +1175,76 @@ describe('platform/github', () => { const res = await github.ensureIssue('title-1', 'newer-content'); expect(res).toBeNull(); }); + it('creates issue if reopen flag false and issue is not open', async () => { + api.post.mockImplementationOnce( + () => + ({ + body: JSON.stringify({ + data: { + repository: { + issues: { + pageInfo: { + startCursor: null, + hasNextPage: false, + endCursor: null, + }, + nodes: [ + { + number: 2, + state: 'close', + title: 'title-2', + }, + ], + }, + }, + }, + }), + } as any) + ); + api.get.mockReturnValueOnce({ body: { body: 'new-content' } } as any); + const res = await github.ensureIssue( + 'title-2', + 'new-content', + false, + false + ); + expect(res).toEqual('created'); + }); + it('does not create issue if reopen flag false and issue is already open', async () => { + api.post.mockImplementationOnce( + () => + ({ + body: JSON.stringify({ + data: { + repository: { + issues: { + pageInfo: { + startCursor: null, + hasNextPage: false, + endCursor: null, + }, + nodes: [ + { + number: 2, + state: 'open', + title: 'title-2', + }, + ], + }, + }, + }, + }), + } as any) + ); + api.get.mockReturnValueOnce({ body: { body: 'new-content' } } as any); + const res = await github.ensureIssue( + 'title-2', + 'new-content', + false, + false + ); + expect(res).toEqual(null); + }); }); describe('ensureIssueClosing()', () => { it('closes issue', async () => { diff --git a/website/docs/configuration-options.md b/website/docs/configuration-options.md index 2fc23429efa6ef171ffff16bc5e6e1a21769f331..0dfbb267eb5a5baa4b1f4a7313c3448e025baf31 100644 --- a/website/docs/configuration-options.md +++ b/website/docs/configuration-options.md @@ -192,6 +192,10 @@ This is used to manually restrict which versions are possible to upgrade to base Warning: composer support is in alpha stage so you probably only want to run this if you are helping get it feature-ready. +## configWarningReuseIssue + +Set this option to false if you prefer Renovate to open a new issue whenever there is config warning. + ## deps-edn ## description