UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

773 lines • 32.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.id = void 0; exports.resetPlatform = resetPlatform; exports.initPlatform = initPlatform; exports.getRepos = getRepos; exports.getRawFile = getRawFile; exports.getJsonFile = getJsonFile; exports.initRepo = initRepo; exports.getPrList = getPrList; exports.findPr = findPr; exports.getPr = getPr; exports.getBranchPr = getBranchPr; exports.getBranchStatus = getBranchStatus; exports.getBranchStatusCheck = getBranchStatusCheck; exports.setBranchStatus = setBranchStatus; exports.findIssue = findIssue; exports.massageMarkdown = massageMarkdown; exports.maxBodyLength = maxBodyLength; exports.ensureIssue = ensureIssue; exports.getIssueList = getIssueList; exports.ensureIssueClosing = ensureIssueClosing; exports.addAssignees = addAssignees; exports.addReviewers = addReviewers; exports.deleteLabel = deleteLabel; exports.ensureComment = ensureComment; exports.ensureCommentRemoval = ensureCommentRemoval; exports.createPr = createPr; exports.updatePr = updatePr; exports.mergePr = mergePr; const tslib_1 = require("tslib"); const node_url_1 = tslib_1.__importDefault(require("node:url")); const is_1 = tslib_1.__importDefault(require("@sindresorhus/is")); const error_messages_1 = require("../../../constants/error-messages"); const logger_1 = require("../../../logger"); const common_1 = require("../../../util/common"); const git = tslib_1.__importStar(require("../../../util/git")); const hostRules = tslib_1.__importStar(require("../../../util/host-rules")); const bitbucket_1 = require("../../../util/http/bitbucket"); const memory_http_cache_provider_1 = require("../../../util/http/cache/memory-http-cache-provider"); const repository_http_cache_provider_1 = require("../../../util/http/cache/repository-http-cache-provider"); const regex_1 = require("../../../util/regex"); const sanitize_1 = require("../../../util/sanitize"); const string_match_1 = require("../../../util/string-match"); const util_1 = require("../util"); const pr_body_1 = require("../utils/pr-body"); const read_only_issue_body_1 = require("../utils/read-only-issue-body"); const comments = tslib_1.__importStar(require("./comments")); const pr_cache_1 = require("./pr-cache"); const schema_1 = require("./schema"); const utils = tslib_1.__importStar(require("./utils")); const utils_1 = require("./utils"); exports.id = 'bitbucket'; const bitbucketHttp = new bitbucket_1.BitbucketHttp(); const BITBUCKET_PROD_ENDPOINT = 'https://api.bitbucket.org/'; let config = {}; function resetPlatform() { config = {}; renovateUserUuid = null; } const defaults = { endpoint: BITBUCKET_PROD_ENDPOINT }; const pathSeparator = '/'; let renovateUserUuid = null; async function initPlatform({ endpoint, username, password, token, }) { if (!(username && password) && !token) { throw new Error('Init: You must configure either a Bitbucket token or username and password'); } if (endpoint && endpoint !== BITBUCKET_PROD_ENDPOINT) { logger_1.logger.warn(`Init: Bitbucket Cloud endpoint should generally be ${BITBUCKET_PROD_ENDPOINT} but is being configured to a different value. Did you mean to use Bitbucket Server?`); defaults.endpoint = endpoint; } (0, bitbucket_1.setBaseUrl)(defaults.endpoint); renovateUserUuid = null; const options = { memCache: false }; if (token) { options.token = token; } else { options.username = username; options.password = password; } try { const { uuid } = (await bitbucketHttp.getJsonUnchecked('/2.0/user', options)).body; renovateUserUuid = uuid; } catch (err) { if (err.statusCode === 403 && err.body?.error?.detail?.required?.includes('account')) { logger_1.logger.warn(`Bitbucket: missing 'account' scope for password`); } else { logger_1.logger.debug({ err }, 'Unknown error fetching Bitbucket user identity'); } } // TODO: Add a connection check that endpoint/username/password combination are valid (#9594) const platformConfig = { endpoint: endpoint ?? BITBUCKET_PROD_ENDPOINT, }; return Promise.resolve(platformConfig); } // Get all repositories that the user has access to async function getRepos(config) { logger_1.logger.debug('Autodiscovering Bitbucket Cloud repositories'); try { let { body: repos } = await bitbucketHttp.getJson(`/2.0/repositories/?role=contributor`, { paginate: true }, schema_1.Repositories); // if autodiscoverProjects is configured // filter the repos list const autodiscoverProjects = config.projects; if (is_1.default.nonEmptyArray(autodiscoverProjects)) { logger_1.logger.debug({ autodiscoverProjects: config.projects }, 'Applying autodiscoverProjects filter'); repos = repos.filter((repo) => repo.projectName && (0, string_match_1.matchRegexOrGlobList)(repo.projectName, autodiscoverProjects)); } return repos.map(({ owner, name }) => `${owner}/${name}`); } catch (err) /* v8 ignore start */ { logger_1.logger.error({ err }, `bitbucket getRepos error`); throw err; } /* v8 ignore stop */ } async function getRawFile(fileName, repoName, branchOrTag) { // See: https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Bworkspace%7D/%7Brepo_slug%7D/src/%7Bcommit%7D/%7Bpath%7D const repo = repoName ?? config.repository; const path = fileName; let finalBranchOrTag = branchOrTag; if (branchOrTag?.includes(pathSeparator)) { // Branch name contains slash, so we have to replace branch name with SHA1 of the head commit; otherwise the API will not work. finalBranchOrTag = await getBranchCommit(branchOrTag); } const url = `/2.0/repositories/${repo}/src/` + (finalBranchOrTag ?? `HEAD`) + `/${path}`; const res = await bitbucketHttp.getText(url, { cacheProvider: repository_http_cache_provider_1.repoCacheProvider, }); return res.body; } async function getJsonFile(fileName, repoName, branchOrTag) { // TODO #22198 const raw = await getRawFile(fileName, repoName, branchOrTag); return (0, common_1.parseJson)(raw, fileName); } // Initialize bitbucket by getting base branch and SHA async function initRepo({ repository, cloneSubmodules, cloneSubmodulesFilter, ignorePrAuthor, bbUseDevelopmentBranch, }) { logger_1.logger.debug(`initRepo("${repository}")`); const opts = hostRules.find({ hostType: 'bitbucket', url: defaults.endpoint, }); config = { repository, ignorePrAuthor, }; let info; let mainBranch; try { const { body: repoInfo } = await bitbucketHttp.getJson(`/2.0/repositories/${repository}`, schema_1.RepoInfo); info = repoInfo; mainBranch = info.mainbranch; if (bbUseDevelopmentBranch) { // Fetch Bitbucket development branch const developmentBranch = (await bitbucketHttp.getJsonUnchecked(`/2.0/repositories/${repository}/branching-model`)).body.development?.branch?.name; if (developmentBranch) { mainBranch = developmentBranch; } } config.defaultBranch = mainBranch; config = { ...config, owner: info.owner, mergeMethod: info.mergeMethod, has_issues: info.has_issues, is_private: info.is_private, }; logger_1.logger.debug(`${repository} owner = ${config.owner}`); } catch (err) /* v8 ignore start */ { if (err.statusCode === 404) { throw new Error(error_messages_1.REPOSITORY_NOT_FOUND); } logger_1.logger.debug({ err }, 'Unknown Bitbucket initRepo error'); throw err; } /* v8 ignore stop */ const { hostname } = node_url_1.default.parse(defaults.endpoint); // Converts API hostnames to their respective HTTP git hosts: // `api.bitbucket.org` to `bitbucket.org` // `api-staging.<host>` to `staging.<host>` // TODO #22198 const hostnameWithoutApiPrefix = (0, regex_1.regEx)(/api[.|-](.+)/).exec(hostname)?.[1]; const auth = opts.token ? `x-token-auth:${opts.token}` : `${opts.username}:${opts.password}`; const url = git.getUrl({ protocol: 'https', auth, hostname: hostnameWithoutApiPrefix, repository, }); await git.initRepo({ ...config, url, cloneSubmodules, cloneSubmodulesFilter, }); const repoConfig = { defaultBranch: mainBranch, isFork: info.isFork, repoFingerprint: (0, util_1.repoFingerprint)(info.uuid, defaults.endpoint), }; return repoConfig; } /* v8 ignore start */ function matchesState(state, desiredState) { if (desiredState === 'all') { return true; } if (desiredState.startsWith('!')) { return state !== desiredState.substring(1); } return state === desiredState; } /* v8 ignore stop */ async function getPrList() { logger_1.logger.trace('getPrList()'); return await pr_cache_1.BitbucketPrCache.getPrs(bitbucketHttp, config.repository, renovateUserUuid); } async function findPr({ branchName, prTitle, state = 'all', includeOtherAuthors, }) { logger_1.logger.debug(`findPr(${branchName}, ${prTitle}, ${state})`); if (includeOtherAuthors) { // PR might have been created by anyone, so don't use the cached Renovate PR list const prs = (await bitbucketHttp.getJsonUnchecked(`/2.0/repositories/${config.repository}/pullrequests?q=source.branch.name="${branchName}"&state=open`, { cacheProvider: memory_http_cache_provider_1.memCacheProvider })).body.values; if (prs.length === 0) { logger_1.logger.debug(`No PR found for branch ${branchName}`); return null; } return utils.prInfo(prs[0]); } const prList = await getPrList(); const pr = prList.find((p) => p.sourceBranch === branchName && (!prTitle || p.title.toUpperCase() === prTitle.toUpperCase()) && matchesState(p.state, state)); if (!pr) { return null; } logger_1.logger.debug(`Found PR #${pr.number}`); /** * Bitbucket doesn't support renaming or reopening declined PRs. * Instead, we have to use comment-driven signals. */ if (pr.state === 'closed') { const reopenComments = await comments.reopenComments(config, pr.number); if (is_1.default.nonEmptyArray(reopenComments)) { if (config.is_private) { // Only workspace members could have commented on a private repository logger_1.logger.debug(`Found '${comments.REOPEN_PR_COMMENT_KEYWORD}' comment from workspace member. Renovate will reopen PR ${pr.number} as a new PR`); return null; } for (const comment of reopenComments) { if (await isAccountMemberOfWorkspace(comment.user, config.repository)) { logger_1.logger.debug(`Found '${comments.REOPEN_PR_COMMENT_KEYWORD}' comment from workspace member. Renovate will reopen PR ${pr.number} as a new PR`); return null; } } } } return pr; } // Gets details for a PR async function getPr(prNo) { const pr = (await bitbucketHttp.getJsonUnchecked(`/2.0/repositories/${config.repository}/pullrequests/${prNo}`, { cacheProvider: memory_http_cache_provider_1.memCacheProvider })).body; /* v8 ignore start */ if (!pr) { return null; } /* v8 ignore stop */ const res = { ...utils.prInfo(pr), }; if (is_1.default.nonEmptyArray(pr.reviewers)) { res.reviewers = pr.reviewers .map(({ uuid }) => uuid) .filter(is_1.default.nonEmptyString); } return res; } const escapeHash = (input) => input?.replace((0, regex_1.regEx)(/#/g), '%23'); // Return the commit SHA for a branch async function getBranchCommit(branchName) { try { const branch = (await bitbucketHttp.getJsonUnchecked(`/2.0/repositories/${config.repository}/refs/branches/${escapeHash(branchName)}`, { cacheProvider: memory_http_cache_provider_1.memCacheProvider })).body; return branch.target.hash; } catch (err) /* v8 ignore start */ { logger_1.logger.debug({ err }, `getBranchCommit('${branchName}') failed'`); return undefined; } /* v8 ignore stop */ } // Returns the Pull Request for a branch. Null if not exists. async function getBranchPr(branchName) { logger_1.logger.debug(`getBranchPr(${branchName})`); const existingPr = await findPr({ branchName, state: 'open', }); return existingPr ? getPr(existingPr.number) : null; } async function getStatus(branchName, memCache = true) { const sha = await getBranchCommit(branchName); const opts = { paginate: true }; /* v8 ignore start: temporary code */ if (memCache) { opts.cacheProvider = memory_http_cache_provider_1.memCacheProvider; } else { opts.memCache = false; } /* v8 ignore stop */ return (await bitbucketHttp.getJsonUnchecked(`/2.0/repositories/${config.repository}/commit/${sha}/statuses`, opts)).body.values; } // Returns the combined status for a branch. async function getBranchStatus(branchName, internalChecksAsSuccess) { logger_1.logger.debug(`getBranchStatus(${branchName})`); const statuses = await getStatus(branchName); logger_1.logger.debug({ branch: branchName, statuses }, 'branch status check result'); if (!statuses.length) { logger_1.logger.debug('empty branch status check result = returning "pending"'); return 'yellow'; } const noOfFailures = statuses.filter((status) => status.state === 'FAILED' || status.state === 'STOPPED').length; if (noOfFailures) { return 'red'; } const noOfPending = statuses.filter((status) => status.state === 'INPROGRESS').length; if (noOfPending) { return 'yellow'; } if (!internalChecksAsSuccess && statuses.every((status) => status.state === 'SUCCESSFUL' && status.key?.startsWith('renovate/'))) { logger_1.logger.debug('Successful checks are all internal renovate/ checks, so returning "pending" branch status'); return 'yellow'; } return 'green'; } const bbToRenovateStatusMapping = { SUCCESSFUL: 'green', INPROGRESS: 'yellow', FAILED: 'red', }; async function getBranchStatusCheck(branchName, context) { const statuses = await getStatus(branchName); const bbState = statuses.find((status) => status.key === context)?.state; // TODO #22198 return bbToRenovateStatusMapping[bbState] || null; } async function setBranchStatus({ branchName, context, description, state, url: targetUrl, }) { const sha = await getBranchCommit(branchName); // TargetUrl can not be empty so default to bitbucket /* v8 ignore next */ const url = targetUrl ?? 'https://bitbucket.org'; const body = { name: context, state: utils.buildStates[state], key: context, description, url, }; await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/commit/${sha}/statuses/build`, { body }); // update status cache await getStatus(branchName, false); } async function findOpenIssues(title) { try { const filters = [ `title=${JSON.stringify(title)}`, '(state = "new" OR state = "open")', ]; if (renovateUserUuid) { filters.push(`reporter.uuid="${renovateUserUuid}"`); } const filter = encodeURIComponent(filters.join(' AND ')); return ((await bitbucketHttp.getJsonUnchecked(`/2.0/repositories/${config.repository}/issues?q=${filter}`, { cacheProvider: memory_http_cache_provider_1.memCacheProvider })).body.values /* v8 ignore start */ || [] /* v8 ignore stop */); } catch (err) /* v8 ignore start */ { logger_1.logger.warn({ err }, 'Error finding issues'); return []; } /* v8 ignore stop */ } async function findIssue(title) { logger_1.logger.debug(`findIssue(${title})`); /* v8 ignore start */ if (!config.has_issues) { logger_1.logger.debug('Issues are disabled - cannot findIssue'); return null; } /* v8 ignore stop */ const issues = await findOpenIssues(title); if (!issues.length) { return null; } const [issue] = issues; return { number: issue.id, body: issue.content?.raw, }; } async function closeIssue(issueNumber) { await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/issues/${issueNumber}`, { body: { state: 'closed' }, }); } function massageMarkdown(input) { // Remove any HTML we use return (0, pr_body_1.smartTruncate)(input, maxBodyLength()) .replace('you tick the rebase/retry checkbox', 'by renaming this PR to start with "rebase!"') .replace('checking the rebase/retry box above', 'renaming the PR to start with "rebase!"') .replace((0, regex_1.regEx)(/<\/?summary>/g), '**') .replace((0, regex_1.regEx)(/<\/?(details|blockquote)>/g), '') .replace((0, regex_1.regEx)(`\n---\n\n.*?<!-- rebase-check -->.*?\n`), '') .replace((0, regex_1.regEx)(/\]\(\.\.\/pull\//g), '](../../pull-requests/') .replace((0, regex_1.regEx)(/<!--renovate-(?:debug|config-hash):.*?-->/g), ''); } function maxBodyLength() { return 50000; } async function ensureIssue({ title, reuseTitle, body, }) { logger_1.logger.debug(`ensureIssue()`); /* v8 ignore start */ if (!config.has_issues) { logger_1.logger.debug('Issues are disabled - cannot ensureIssue'); logger_1.logger.debug(`Failed to ensure Issue with title:${title}`); return null; } /* v8 ignore stop */ try { let issues = await findOpenIssues(title); const description = massageMarkdown((0, sanitize_1.sanitize)(body)); if (!issues.length && reuseTitle) { issues = await findOpenIssues(reuseTitle); } if (issues.length) { // Close any duplicates for (const issue of issues.slice(1)) { await closeIssue(issue.id); } const [issue] = issues; if (issue.title !== title || String(issue.content?.raw).trim() !== description.trim()) { logger_1.logger.debug('Issue updated'); await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/issues/${issue.id}`, { body: { content: { raw: (0, read_only_issue_body_1.readOnlyIssueBody)(description), markup: 'markdown', }, }, }); return 'updated'; } } else { logger_1.logger.info('Issue created'); await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/issues`, { body: { title, content: { raw: (0, read_only_issue_body_1.readOnlyIssueBody)(description), markup: 'markdown', }, }, }); return 'created'; } } catch (err) /* v8 ignore start */ { if (err.message.startsWith('Repository has no issue tracker.')) { logger_1.logger.debug(`Issues are disabled, so could not create issue: ${title}`); } else { logger_1.logger.warn({ err }, 'Could not ensure issue'); } } /* v8 ignore stop */ return null; } /* v8 ignore start */ async function getIssueList() { logger_1.logger.debug(`getIssueList()`); if (!config.has_issues) { logger_1.logger.debug('Issues are disabled - cannot getIssueList'); return []; } try { const filters = ['(state = "new" OR state = "open")']; if (renovateUserUuid) { filters.push(`reporter.uuid="${renovateUserUuid}"`); } const filter = encodeURIComponent(filters.join(' AND ')); const url = `/2.0/repositories/${config.repository}/issues?q=${filter}`; const res = await bitbucketHttp.getJsonUnchecked(url, { cacheProvider: repository_http_cache_provider_1.repoCacheProvider, }); return res.body.values || []; } catch (err) { logger_1.logger.warn({ err }, 'Error finding issues'); return []; } } /* v8 ignore stop */ async function ensureIssueClosing(title) { /* v8 ignore start */ if (!config.has_issues) { logger_1.logger.debug('Issues are disabled - cannot ensureIssueClosing'); return; } /* v8 ignore stop */ const issues = await findOpenIssues(title); for (const issue of issues) { await closeIssue(issue.id); } } function addAssignees(_prNr, _assignees) { // Bitbucket supports "participants" and "reviewers" so does not seem to have the concept of "assignee" logger_1.logger.warn('Cannot add assignees'); return Promise.resolve(); } async function addReviewers(prId, reviewers) { logger_1.logger.debug(`Adding reviewers '${reviewers.join(', ')}' to #${prId}`); // TODO #22198 const { title } = (await getPr(prId)); const body = { title, reviewers: reviewers.map((username) => { const isUUID = username.startsWith('{') && username.endsWith('}') && string_match_1.UUIDRegex.test(username.slice(1, -1)); const key = isUUID ? 'uuid' : 'username'; return { [key]: username, }; }), }; await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/pullrequests/${prId}`, { body, }); } /* v8 ignore start */ function deleteLabel() { throw new Error('deleteLabel not implemented'); } /* v8 ignore stop */ function ensureComment({ number, topic, content, }) { // https://developer.atlassian.com/bitbucket/api/2/reference/search?q=pullrequest+comment return comments.ensureComment({ config, number, topic, content: (0, sanitize_1.sanitize)(content), }); } function ensureCommentRemoval(deleteConfig) { return comments.ensureCommentRemoval(config, deleteConfig); } async function sanitizeReviewers(reviewers, err) { if (err.statusCode === 400 && err.body?.error?.fields?.reviewers) { const sanitizedReviewers = []; const MSG_AUTHOR_AND_REVIEWER = 'is the author and cannot be included as a reviewer.'; const MSG_MALFORMED_REVIEWERS_LIST = 'Malformed reviewers list'; const MSG_NOT_WORKSPACE_MEMBER = 'is not a member of this workspace and cannot be added to this pull request'; for (const msg of err.body.error.fields.reviewers) { // Bitbucket returns a 400 if any of the PR reviewer accounts are now inactive (ie: disabled/suspended) if (msg === MSG_MALFORMED_REVIEWERS_LIST) { logger_1.logger.debug({ err }, 'PR contains reviewers that may be either inactive or no longer a member of this workspace. Will try setting only active reviewers'); // Validate that each previous PR reviewer account is still active for (const reviewer of reviewers) { const reviewerUser = (await bitbucketHttp.getJsonUnchecked(`/2.0/users/${reviewer.uuid}`, { cacheProvider: memory_http_cache_provider_1.memCacheProvider })).body; if (reviewerUser.account_status === 'active') { // There are cases where an active user may still not be a member of a workspace if (await isAccountMemberOfWorkspace(reviewer, config.repository)) { sanitizedReviewers.push(reviewer); } } } // Bitbucket returns a 400 if any of the PR reviewer accounts are no longer members of this workspace } else if (msg.endsWith(MSG_NOT_WORKSPACE_MEMBER)) { logger_1.logger.debug({ err }, 'PR contains reviewer accounts which are no longer member of this workspace. Will try setting only member reviewers'); // Validate that each previous PR reviewer account is still a member of this workspace for (const reviewer of reviewers) { if (await isAccountMemberOfWorkspace(reviewer, config.repository)) { sanitizedReviewers.push(reviewer); } } } else if (msg.endsWith(MSG_AUTHOR_AND_REVIEWER)) { logger_1.logger.debug({ err }, 'PR contains reviewer accounts which are also the author. Will try setting only non-author reviewers'); const author = msg.replace(MSG_AUTHOR_AND_REVIEWER, '').trim(); for (const reviewer of reviewers) { if (reviewer.display_name !== author) { sanitizedReviewers.push(reviewer); } } } else { return undefined; } } return sanitizedReviewers; } return undefined; } async function isAccountMemberOfWorkspace(reviewer, repository) { const workspace = repository.split('/')[0]; try { await bitbucketHttp.get(`/2.0/workspaces/${workspace}/members/${reviewer.uuid}`, { cacheProvider: memory_http_cache_provider_1.memCacheProvider }); return true; } catch (err) { // HTTP 404: User cannot be found, or the user is not a member of this workspace. if (err.statusCode === 404) { logger_1.logger.debug({ err }, `User ${reviewer.display_name} is not a member of the workspace ${workspace}. Will be removed from the PR`); return false; } throw err; } } // Creates PR and returns PR number async function createPr({ sourceBranch, targetBranch, prTitle: title, prBody: description, platformPrOptions, }) { // labels is not supported in Bitbucket: https://bitbucket.org/site/master/issues/11976/ability-to-add-labels-to-pull-requests-bb const base = targetBranch; logger_1.logger.debug({ repository: config.repository, title, base }, 'Creating PR'); let reviewers = []; if (platformPrOptions?.bbUseDefaultReviewers) { const reviewersResponse = (await bitbucketHttp.getJsonUnchecked(`/2.0/repositories/${config.repository}/effective-default-reviewers`, { paginate: true, cacheProvider: memory_http_cache_provider_1.memCacheProvider, })).body; reviewers = reviewersResponse.values.map((reviewer) => ({ uuid: reviewer.user.uuid, display_name: reviewer.user.display_name, })); } const body = { title, description: (0, sanitize_1.sanitize)(description), source: { branch: { name: sourceBranch, }, }, destination: { branch: { name: base, }, }, close_source_branch: true, reviewers, }; try { const prRes = (await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/pullrequests`, { body, })).body; const pr = utils.prInfo(prRes); await pr_cache_1.BitbucketPrCache.setPr(bitbucketHttp, config.repository, renovateUserUuid, pr); if (platformPrOptions?.bbAutoResolvePrTasks) { await autoResolvePrTasks(pr); } return pr; } catch (err) /* v8 ignore start */ { // Try sanitizing reviewers const sanitizedReviewers = await sanitizeReviewers(reviewers, err); if (sanitizedReviewers === undefined) { logger_1.logger.warn({ err }, 'Error creating pull request'); throw err; } else { const prRes = (await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/pullrequests`, { body: { ...body, reviewers: sanitizedReviewers, }, })).body; const pr = utils.prInfo(prRes); await pr_cache_1.BitbucketPrCache.setPr(bitbucketHttp, config.repository, renovateUserUuid, pr); if (platformPrOptions?.bbAutoResolvePrTasks) { await autoResolvePrTasks(pr); } return pr; } } /* v8 ignore stop */ } async function autoResolvePrTasks(pr) { logger_1.logger.debug(`Auto resolve PR tasks in #${pr.number}`); try { const unResolvedTasks = (await bitbucketHttp.getJson(`/2.0/repositories/${config.repository}/pullrequests/${pr.number}/tasks`, { paginate: true, pagelen: 100 }, schema_1.UnresolvedPrTasks)).body; logger_1.logger.trace({ prId: pr.number, listTaskRes: unResolvedTasks, }, 'List PR tasks'); for (const task of unResolvedTasks) { const res = await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/pullrequests/${pr.number}/tasks/${task.id}`, { body: { state: 'RESOLVED', content: { raw: task.content.raw, }, }, }); logger_1.logger.trace({ prId: pr.number, updateTaskResponse: res, }, 'Put PR tasks - mark resolved'); } } catch (err) { logger_1.logger.warn({ prId: pr.number, err }, 'Error resolving PR tasks'); } } async function updatePr({ number: prNo, prTitle: title, prBody: description, state, targetBranch, }) { logger_1.logger.debug(`updatePr(${prNo}, ${title}, body)`); // Updating a PR in Bitbucket will clear the reviewers if reviewers is not present const pr = (await bitbucketHttp.getJsonUnchecked(`/2.0/repositories/${config.repository}/pullrequests/${prNo}`)).body; let updatedPrRes; try { const body = { title, description: (0, sanitize_1.sanitize)(description), reviewers: pr.reviewers, }; if (targetBranch) { body.destination = { branch: { name: targetBranch, }, }; } updatedPrRes = (await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/pullrequests/${prNo}`, { body })).body; } catch (err) { // Try sanitizing reviewers const sanitizedReviewers = await sanitizeReviewers(pr.reviewers, err); if (sanitizedReviewers === undefined) { throw err; } else { updatedPrRes = (await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/pullrequests/${prNo}`, { body: { title, description: (0, sanitize_1.sanitize)(description), reviewers: sanitizedReviewers, }, })).body; } } if (state === 'closed' && pr) { await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/pullrequests/${prNo}/decline`); } // update pr cache await pr_cache_1.BitbucketPrCache.setPr(bitbucketHttp, config.repository, renovateUserUuid, utils.prInfo({ ...updatedPrRes, ...(state && { state }) })); } async function mergePr({ branchName, id: prNo, strategy: mergeStrategy, }) { logger_1.logger.debug(`mergePr(${prNo}, ${branchName}, ${mergeStrategy})`); // Bitbucket Cloud does not support a rebase-alike; https://jira.atlassian.com/browse/BCLOUD-16610 if (mergeStrategy === 'rebase') { logger_1.logger.warn('Bitbucket Cloud does not support a "rebase" strategy.'); return false; } try { await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/pullrequests/${prNo}/merge`, { body: (0, utils_1.mergeBodyTransformer)(mergeStrategy), }); logger_1.logger.debug('Automerging succeeded'); } catch (err) /* v8 ignore start */ { logger_1.logger.debug({ err }, `PR merge error`); logger_1.logger.info({ pr: prNo }, 'PR automerge failed'); return false; } /* v8 ignore stop */ return true; } //# sourceMappingURL=index.js.map