import url from 'url' ; import is from '@sindresorhus/is' ; import delay from 'delay' ; import JSON5 from 'json5' ; import type { PartialDeep } from 'type-fest' ; import { PlatformId } from '../../constants' ; import { REPOSITORY_CHANGED , REPOSITORY_EMPTY , REPOSITORY_NOT_FOUND , } from '../../constants/error-messages' ; import { logger } from '../../logger' ; import { BranchStatus , PrState , VulnerabilityAlert } from '../../types' ; import { GitProtocol } from '../../types/git' ; import type { FileData } from '../../types/platform/bitbucket-server' ; import * as git from '../../util/git' ; import { deleteBranch } from '../../util/git' ; import * as hostRules from '../../util/host-rules' ; import { HttpResponse } from '../../util/http' ; import { BitbucketServerHttp , setBaseUrl , } from '../../util/http/bitbucket-server' ; import { regEx } from '../../util/regex' ; import { sanitize } from '../../util/sanitize' ; import { ensureTrailingSlash , getQueryString } from '../../util/url' ; import type { BranchStatusConfig , CreatePRConfig , EnsureCommentConfig , EnsureCommentRemovalConfig , EnsureIssueConfig , EnsureIssueResult , FindPRConfig , Issue , MergePRConfig , PlatformParams , PlatformResult , Pr , RepoParams , RepoResult , UpdatePrConfig , } from '../types' ; import { smartTruncate } from '../utils/pr-body' ; import type { BbsConfig , BbsPr , BbsRestBranch , BbsRestPr , BbsRestRepo , BbsRestUserRef , } from './types' ; import * as utils from './utils' ; let config = { } as any ; const bitbucketServerHttp = new BitbucketServerHttp ( ) ; const defaults = { hostType : PlatformId . BitbucketServer , } ; function updatePrVersion ( pr , version ) { const res = Math . max ( config . prVersions . get ( pr ) || 0 , version ) ; config . prVersions . set ( pr , res ) ; return res ; } export function initPlatform ( { endpoint , username , password , } ) { if ( ! endpoint ) { throw new Error ( 'Init: You must configure a Bitbucket Server endpoint' ) ; } if ( ! ( username && password ) ) { throw new Error ( 'Init: You must configure a Bitbucket Server username/password' ) ; } defaults . endpoint = ensureTrailingSlash ( endpoint ) ; setBaseUrl ( defaults . endpoint ) ; const platformConfig = { endpoint : defaults . endpoint , } ; return Promise . resolve ( platformConfig ) ; } export async function getRepos ( ) { logger . debug ( 'Autodiscovering Bitbucket Server repositories' ) ; try { const repos = await utils . accumulateValues ( `./rest/api/1.0/repos?permission=REPO_WRITE&state=AVAILABLE` ) ; const result = repos . map ( ( r ) => `${ r . project . key . toLowerCase ( ) }/${ r . slug }` ) ; logger . debug ( { result } , 'result of getRepos()' ) ; return result ; } catch ( err ) { logger . error ( { err } , `bitbucket getRepos error` ) ; throw err ; } } export async function getRawFile ( fileName , repoName ? , branchOrTag ? ) { const repo = repoName ?? config . repository ; const [ project , slug ] = repo . split ( '/' ) ; const fileUrl = `./rest/api/1.0/projects/${ project }/repos/${ slug }/browse/${ fileName }?limit=20000` + ( branchOrTag ? '&at=' + branchOrTag : '' ) ; const res = await bitbucketServerHttp . getJson < FileData > ( fileUrl ) ; const { isLastPage , lines , size } = res . body ; if ( isLastPage ) { return lines . map ( ( { text } ) => text ) . join ( '' ) ; } const msg = `The file is too big (${ size }B)` ; logger . warn ( { size } , msg ) ; throw new Error ( msg ) ; } export async function getJsonFile ( fileName , repoName ? , branchOrTag ? ) { const raw = await getRawFile ( fileName , repoName , branchOrTag ) ; if ( fileName . endsWith ( '.json5' ) ) { return JSON5 . parse ( raw ) ; } return JSON . parse ( raw ) ; } export async function initRepo ( { repository , cloneSubmodules , ignorePrAuthor , } ) { logger . debug ( `initRepo("${ JSON . stringify ( { repository } , null , 2 ) }")` ) ; const opts = hostRules . find ( { hostType : defaults . hostType , url : defaults . endpoint , } ) ; const [ projectKey , repositorySlug ] = repository . split ( '/' ) ; config = { projectKey , repositorySlug , repository , prVersions : new Map < number , number > ( ) , username : opts . username , ignorePrAuthor , } as any ; try { const info = ( await bitbucketServerHttp . getJson < BbsRestRepo > ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }` ) ) . body ; config . owner = info . project . key ; logger . debug ( `${ repository } owner = ${ config . owner }` ) ; const branchRes = await bitbucketServerHttp . getJson < BbsRestBranch > ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }/branches/default` ) ; if ( [ 204 , 404 ] . includes ( branchRes . statusCode ) ) { throw new Error ( REPOSITORY_EMPTY ) ; } let cloneUrl = info . links . clone ?. find ( ( { name } ) => name === 'http' ) ; if ( ! cloneUrl ) { cloneUrl = info . links . clone ?. find ( ( { name } ) => name === 'ssh' ) ; } let gitUrl ; if ( ! cloneUrl ) { const { host , pathname } = url . parse ( defaults . endpoint ) ; gitUrl = git . getUrl ( { protocol : defaults . endpoint . split ( ':' ) [ 0 ] as GitProtocol , auth : `${ opts . username }:${ opts . password }` , host : `${ host }${ pathname }${ pathname . endsWith ( '/' ) ? '' : '/' }scm` , repository , } ) ; } else if ( cloneUrl . name === 'http' ) { const repoUrl = url . parse ( cloneUrl . href ) ; repoUrl . auth = `${ opts . username }:${ opts . password }` ; gitUrl = url . format ( repoUrl ) ; } else { gitUrl = cloneUrl . href ; } await git . initRepo ( { ... config , url : gitUrl , cloneSubmodules , fullClone : true , } ) ; config . mergeMethod = 'merge' ; const repoConfig = { defaultBranch : branchRes . body . displayId , isFork : ! ! info . origin , } ; return repoConfig ; } catch ( err ) { if ( err . statusCode === 404 ) { throw new Error ( REPOSITORY_NOT_FOUND ) ; } if ( err . message === REPOSITORY_EMPTY ) { throw err ; } logger . debug ( { err } , 'Unknown Bitbucket initRepo error' ) ; throw err ; } } export async function getRepoForceRebase ( ) { logger . debug ( `getRepoForceRebase()` ) ; const res = await bitbucketServerHttp . getJson < { mergeConfig : { defaultStrategy : { id : string ; } ; } ; } > ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }/settings/pull-requests` ) ; return Boolean ( res . body ?. mergeConfig ?. defaultStrategy ?. id . includes ( 'ff-only' ) ) ; } export async function getPr ( prNo , refreshCache ? ) { logger . debug ( `getPr(${ prNo })` ) ; if ( ! prNo ) { return null ; } const res = await bitbucketServerHttp . getJson < BbsRestPr > ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }/pull-requests/${ prNo }` , { useCache : ! refreshCache } ) ; const pr = { displayNumber : `Pull Request #${ res . body . id }` , ... utils . prInfo ( res . body ) , reviewers : res . body . reviewers . map ( ( r ) => r . user . name ) , } ; pr . hasReviewers = is . nonEmptyArray ( pr . reviewers ) ; pr . version = updatePrVersion ( pr . number , pr . version ) ; if ( pr . state === PrState . Open ) { const mergeRes = await bitbucketServerHttp . getJson < { conflicted : string ; canMerge : string ; } > ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }/pull-requests/${ prNo }/merge` , { useCache : ! refreshCache } ) ; pr . isConflicted = ! ! mergeRes . body . conflicted ; pr . canMerge = ! ! mergeRes . body . canMerge ; } return pr ; } function matchesState ( state , desiredState ) { if ( desiredState === PrState . All ) { return true ; } if ( desiredState . startsWith ( '!' ) ) { return state !== desiredState . substring ( 1 ) ; } return state === desiredState ; } const isRelevantPr = ( branchName , prTitle , state ) => ( p ) : boolean => p . sourceBranch === branchName && ( ! prTitle || p . title === prTitle ) && matchesState ( p . state , state ) ; export async function getPrList ( refreshCache ? ) { logger . debug ( `getPrList()` ) ; if ( ! config . prList || refreshCache ) { const searchParams = { state : 'ALL' , } ; if ( ! config . ignorePrAuthor ) { searchParams [ 'role.1' ] = 'AUTHOR' ; searchParams [ 'username.1' ] = config . username ; } const query = getQueryString ( searchParams ) ; const values = await utils . accumulateValues ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }/pull-requests?${ query }` ) ; config . prList = values . map ( utils . prInfo ) ; logger . debug ( { length : config . prList . length } , 'Retrieved Pull Requests' ) ; } else { logger . debug ( 'returning cached PR list' ) ; } return config . prList ; } export async function findPr ( { branchName , prTitle , state = PrState . All , refreshCache , } ) { logger . debug ( `findPr(${ branchName }, "${ prTitle }", "${ state }")` ) ; const prList = await getPrList ( refreshCache ) ; const pr = prList . find ( isRelevantPr ( branchName , prTitle , state ) ) ; if ( pr ) { logger . debug ( `Found PR #${ pr . number }` ) ; } else { logger . debug ( `DID NOT Found PR from branch #${ branchName }` ) ; } return pr ; } export async function getBranchPr ( branchName ) { logger . debug ( `getBranchPr(${ branchName })` ) ; const existingPr = await findPr ( { branchName , state : PrState . Open , } ) ; return existingPr ? getPr ( existingPr . number ) : null ; } export async function refreshPr ( number ) { await delay ( 1000 ) ; await getPr ( number , true ) ; } async function getStatus ( branchName , useCache = true ) { const branchCommit = git . getBranchCommit ( branchName ) ; return ( await bitbucketServerHttp . getJson < utils . BitbucketCommitStatus > ( `./rest/build-status/1.0/commits/stats/${ branchCommit }` , { useCache , } ) ) . body ; } export async function getBranchStatus ( branchName ) { logger . debug ( `getBranchStatus(${ branchName })` ) ; if ( ! git . branchExists ( branchName ) ) { logger . debug ( 'Branch does not exist - cannot fetch status' ) ; throw new Error ( REPOSITORY_CHANGED ) ; } try { const commitStatus = await getStatus ( branchName ) ; logger . debug ( { commitStatus } , 'branch status check result' ) ; if ( commitStatus . failed > 0 ) { return BranchStatus . red ; } if ( commitStatus . inProgress > 0 ) { return BranchStatus . yellow ; } return commitStatus . successful > 0 ? BranchStatus . green : BranchStatus . yellow ; } catch ( err ) { logger . warn ( { err } , `Failed to get branch status` ) ; return BranchStatus . red ; } } function getStatusCheck ( branchName , useCache = true ) { const branchCommit = git . getBranchCommit ( branchName ) ; return utils . accumulateValues ( `./rest/build-status/1.0/commits/${ branchCommit }` , 'get' , { useCache } ) ; } export async function getBranchStatusCheck ( branchName , context ) { logger . debug ( `getBranchStatusCheck(${ branchName }, context=${ context })` ) ; try { const states = await getStatusCheck ( branchName ) ; for ( const state of states ) { if ( state . key === context ) { switch ( state . state ) { case 'SUCCESSFUL' : return BranchStatus . green ; case 'INPROGRESS' : return BranchStatus . yellow ; case 'FAILED' : default : return BranchStatus . red ; } } } } catch ( err ) { logger . warn ( { err } , `Failed to check branch status` ) ; } return null ; } export async function setBranchStatus ( { branchName , context , description , state , url : targetUrl , } ) { logger . debug ( `setBranchStatus(${ branchName })` ) ; const existingStatus = await getBranchStatusCheck ( branchName , context ) ; if ( existingStatus === state ) { return ; } logger . debug ( { branch : branchName , context , state } , 'Setting branch status' ) ; const branchCommit = git . getBranchCommit ( branchName ) ; try { const body = { key : context , description , url : targetUrl || 'https://renovatebot.com' , } ; switch ( state ) { case BranchStatus . green : body . state = 'SUCCESSFUL' ; break ; case BranchStatus . yellow : body . state = 'INPROGRESS' ; break ; case BranchStatus . red : default : body . state = 'FAILED' ; break ; } await bitbucketServerHttp . postJson ( `./rest/build-status/1.0/commits/${ branchCommit }` , { body } ) ; await getStatus ( branchName , false ) ; await getStatusCheck ( branchName , false ) ; } catch ( err ) { logger . warn ( { err } , `Failed to set branch status` ) ; } } export function findIssue ( title ) { logger . debug ( `findIssue(${ title })` ) ; return null ; } export function ensureIssue ( { title , } ) { logger . warn ( { title } , 'Cannot ensure issue' ) ; return null ; } export function getIssueList ( ) { logger . debug ( `getIssueList()` ) ; return Promise . resolve ( [ ] ) ; } export function ensureIssueClosing ( title ) { logger . debug ( `ensureIssueClosing(${ title })` ) ; return Promise . resolve ( ) ; } export function addAssignees ( iid , assignees ) { logger . debug ( `addAssignees(${ iid }, [${ assignees . join ( ', ' ) }])` ) ; return Promise . resolve ( ) ; } export async function addReviewers ( prNo , reviewers ) { logger . debug ( `Adding reviewers '${ reviewers . join ( ', ' ) }' to #${ prNo }` ) ; try { const pr = await getPr ( prNo ) ; if ( ! pr ) { throw new Error ( REPOSITORY_NOT_FOUND ) ; } const reviewersSet = new Set ( [ ... pr . reviewers , ... reviewers ] ) ; await bitbucketServerHttp . putJson ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }/pull-requests/${ prNo }` , { body : { title : pr . title , version : pr . version , reviewers : Array . from ( reviewersSet ) . map ( ( name ) => ( { user : { name } , } ) ) , } , } ) ; await getPr ( prNo , true ) ; } catch ( err ) { logger . warn ( { err , reviewers , prNo } , `Failed to add reviewers` ) ; if ( err . statusCode === 404 ) { throw new Error ( REPOSITORY_NOT_FOUND ) ; } else if ( err . statusCode === 409 && ! utils . isInvalidReviewersResponse ( err ) ) { logger . debug ( '409 response to adding reviewers - has repository changed?' ) ; throw new Error ( REPOSITORY_CHANGED ) ; } else { throw err ; } } } export function deleteLabel ( issueNo , label ) { logger . debug ( `deleteLabel(${ issueNo }, ${ label })` ) ; return Promise . resolve ( ) ; } type Comment = { text : string ; id : number ; } ; async function getComments ( prNo ) { let comments = await utils . accumulateValues ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }/pull-requests/${ prNo }/activities` ) ; comments = comments . filter ( ( a ) => a . action === 'COMMENTED' && a . commentAction === 'ADDED' ) . map ( ( a ) => a . comment ) ; logger . debug ( `Found ${ comments . length } comments` ) ; return comments ; } async function addComment ( prNo , text ) { await bitbucketServerHttp . postJson ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }/pull-requests/${ prNo }/comments` , { body : { text } , } ) ; } async function getCommentVersion ( prNo , commentId ) { const { version } = ( await bitbucketServerHttp . getJson < { version : number ; } > ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }/pull-requests/${ prNo }/comments/${ commentId }` ) ) . body ; return version ; } async function editComment ( prNo , commentId , text ) { const version = await getCommentVersion ( prNo , commentId ) ; await bitbucketServerHttp . putJson ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }/pull-requests/${ prNo }/comments/${ commentId }` , { body : { text , version } , } ) ; } async function deleteComment ( prNo , commentId ) { const version = await getCommentVersion ( prNo , commentId ) ; await bitbucketServerHttp . deleteJson ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }/pull-requests/${ prNo }/comments/${ commentId }?version=${ version }` ) ; } export async function ensureComment ( { number , topic , content , } ) { const sanitizedContent = sanitize ( content ) ; try { const comments = await getComments ( number ) ; let body ; let commentId ; let commentNeedsUpdating ; if ( topic ) { logger . debug ( `Ensuring comment "${ topic }" in #${ number }` ) ; body = `### ${ topic }\n\n${ sanitizedContent }` ; comments . forEach ( ( comment ) => { if ( comment . text . startsWith ( `### ${ topic }\n\n` ) ) { commentId = comment . id ; commentNeedsUpdating = comment . text !== body ; } } ) ; } else { logger . debug ( `Ensuring content-only comment in #${ number }` ) ; body = `${ sanitizedContent }` ; comments . forEach ( ( comment ) => { if ( comment . text === body ) { commentId = comment . id ; commentNeedsUpdating = false ; } } ) ; } if ( ! commentId ) { await addComment ( number , body ) ; logger . info ( { repository : config . repository , prNo : number , topic } , 'Comment added' ) ; } else if ( commentNeedsUpdating ) { await editComment ( number , commentId , body ) ; logger . debug ( { repository : config . repository , prNo : number } , 'Comment updated' ) ; } else { logger . debug ( 'Comment is already update-to-date' ) ; } return true ; } catch ( err ) { logger . warn ( { err } , 'Error ensuring comment' ) ; return false ; } } export async function ensureCommentRemoval ( { number : prNo , topic , content , } ) { try { logger . debug ( `Ensuring comment "${ topic || content }" in #${ prNo } is removed` ) ; const comments = await getComments ( prNo ) ; const byTopic = ( comment ) : boolean => comment . text . startsWith ( `### ${ topic }\n\n` ) ; const byContent = ( comment ) : boolean => comment . text . trim ( ) === content ; let commentId = null ; if ( topic ) { commentId = comments . find ( byTopic ) ?. id ; } else if ( content ) { commentId = comments . find ( byContent ) ?. id ; } if ( commentId ) { await deleteComment ( prNo , commentId ) ; } } catch ( err ) { logger . warn ( { err } , 'Error ensuring comment removal' ) ; } } const escapeHash = ( input ) : string => input ? input . replace ( regEx ( /#/g ) , '%23' ) : input ; export async function createPr ( { sourceBranch , targetBranch , prTitle : title , prBody : rawDescription , platformOptions , } ) { const description = sanitize ( rawDescription ) ; logger . debug ( `createPr(${ sourceBranch }, title=${ title })` ) ; const base = targetBranch ; let reviewers = [ ] ; if ( platformOptions ?. bbUseDefaultReviewers ) { logger . debug ( `fetching default reviewers` ) ; const { id } = ( await bitbucketServerHttp . getJson < { id : number ; } > ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }` ) ) . body ; const defReviewers = ( await bitbucketServerHttp . getJson < { name : string ; } [ ] > ( `./rest/default-reviewers/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }/reviewers?sourceRefId=refs/heads/${ escapeHash ( sourceBranch ) }&targetRefId=refs/heads/${ base }&sourceRepoId=${ id }&targetRepoId=${ id }` ) ) . body ; reviewers = defReviewers . map ( ( u ) => ( { user : { name : u . name } , } ) ) ; } const body = { title , description , fromRef : { id : `refs/heads/${ sourceBranch }` , } , toRef : { id : `refs/heads/${ base }` , } , reviewers , } ; let prInfoRes ; try { prInfoRes = await bitbucketServerHttp . postJson < BbsRestPr > ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }/pull-requests` , { body } ) ; } catch ( err ) { if ( err . body ?. errors ?. [ 0 ] ?. exceptionName === 'com.atlassian.bitbucket.pull.EmptyPullRequestException' ) { logger . debug ( 'Empty pull request - deleting branch so it can be recreated next run' ) ; await deleteBranch ( sourceBranch ) ; throw new Error ( REPOSITORY_CHANGED ) ; } throw err ; } const pr = { displayNumber : `Pull Request #${ prInfoRes . body . id }` , ... utils . prInfo ( prInfoRes . body ) , } ; updatePrVersion ( pr . number , pr . version ) ; if ( config . prList ) { config . prList . push ( pr ) ; } return pr ; } export async function updatePr ( { number : prNo , prTitle : title , prBody : rawDescription , state , bitbucketInvalidReviewers , } ) { const description = sanitize ( rawDescription ) ; logger . debug ( `updatePr(${ prNo }, title=${ title })` ) ; try { const pr = await getPr ( prNo ) ; if ( ! pr ) { throw Object . assign ( new Error ( REPOSITORY_NOT_FOUND ) , { statusCode : 404 } ) ; } const { body : updatedPr } = await bitbucketServerHttp . putJson < { version : number ; state : string ; } > ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }/pull-requests/${ prNo }` , { body : { title , description , version : pr . version , reviewers : pr . reviewers . filter ( ( name ) => ! bitbucketInvalidReviewers ?. includes ( name ) ) . map ( ( name ) => ( { user : { name } } ) ) , } , } ) ; updatePrVersion ( prNo , updatedPr . version ) ; const currentState = updatedPr . state ; const newState = { [ PrState . Open ] : 'OPEN' , [ PrState . Closed ] : 'DECLINED' , } [ state ] ; if ( newState && [ 'OPEN' , 'DECLINED' ] . includes ( currentState ) && currentState !== newState ) { const command = state === PrState . Open ? 'reopen' : 'decline' ; const { body : updatedStatePr } = await bitbucketServerHttp . postJson < { version : number ; } > ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }/pull-requests/${ pr . number }/${ command }?version=${ updatedPr . version }` ) ; updatePrVersion ( pr . number , updatedStatePr . version ) ; } } catch ( err ) { logger . debug ( { err , prNo } , `Failed to update PR` ) ; if ( err . statusCode === 404 ) { throw new Error ( REPOSITORY_NOT_FOUND ) ; } else if ( err . statusCode === 409 ) { if ( utils . isInvalidReviewersResponse ( err ) && ! bitbucketInvalidReviewers ) { const invalidReviewers = utils . getInvalidReviewers ( err ) ; await updatePr ( { number : prNo , prTitle : title , prBody : rawDescription , state , bitbucketInvalidReviewers : invalidReviewers , } ) ; } else { throw new Error ( REPOSITORY_CHANGED ) ; } } else { throw err ; } } } export async function mergePr ( { branchName , id : prNo , } ) { logger . debug ( `mergePr(${ prNo }, ${ branchName })` ) ; try { const pr = await getPr ( prNo ) ; if ( ! pr ) { throw Object . assign ( new Error ( REPOSITORY_NOT_FOUND ) , { statusCode : 404 } ) ; } const { body } = await bitbucketServerHttp . postJson < { version : number ; } > ( `./rest/api/1.0/projects/${ config . projectKey }/repos/${ config . repositorySlug }/pull-requests/${ prNo }/merge?version=${ pr . version }` ) ; updatePrVersion ( prNo , body . version ) ; } catch ( err ) { if ( err . statusCode === 404 ) { throw new Error ( REPOSITORY_NOT_FOUND ) ; } else if ( err . statusCode === 409 ) { logger . warn ( { err } , `Failed to merge PR` ) ; return false ; } else { logger . warn ( { err } , `Failed to merge PR` ) ; return false ; } } logger . debug ( { pr : prNo } , 'PR merged' ) ; return true ; } export function massageMarkdown ( input ) { logger . debug ( `massageMarkdown(${ input . split ( '\n' ) [ 0 ] })` ) ; return smartTruncate ( input , 30000 ) . replace ( 'you tick the rebase/retry checkbox' , 'rename PR to start with "rebase!"' ) . replace ( regEx ( /<\/?summary>/g ) , '**' ) . replace ( regEx ( /<\/?details>/g ) , '' ) . replace ( regEx ( `\n---\n\n.*?<!-- rebase-check -->.*?(\n|$)` ) , '' ) . replace ( regEx ( '<!--.*?-->' , 'g' ) , '' ) ; } export function getVulnerabilityAlerts ( ) { logger . debug ( `getVulnerabilityAlerts()` ) ; return Promise . resolve ( [ ] ) ; }