UNPKG

@salesforce/plugin-release-management

Version:
123 lines 5.91 kB
/* * Copyright (c) 2020, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, camelcase*/ import { Flags, SfCommand } from '@salesforce/sf-plugins-core'; import { Octokit } from '@octokit/core'; import { Env } from '@salesforce/kit'; import { ensureString } from '@salesforce/ts-types'; import { Messages } from '@salesforce/core'; import { maxVersionBumpFlag, getOwnerAndRepo } from '../../dependabot.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-release-management', 'dependabot.automerge'); const messagesFromConsolidate = Messages.loadMessages('@salesforce/plugin-release-management', 'dependabot.consolidate'); export default class AutoMerge extends SfCommand { static summary = messages.getMessage('description'); static description = messages.getMessage('description'); static examples = messages.getMessages('examples'); static flags = { owner: Flags.string({ // eslint-disable-next-line sf-plugin/dash-o char: 'o', summary: messagesFromConsolidate.getMessage('owner'), dependsOn: ['repo'], }), repo: Flags.string({ char: 'r', summary: messagesFromConsolidate.getMessage('repo'), dependsOn: ['owner'], }), 'max-version-bump': maxVersionBumpFlag, dryrun: Flags.boolean({ summary: messagesFromConsolidate.getMessage('dryrun'), char: 'd', default: false, }), 'skip-ci': Flags.boolean({ summary: messages.getMessage('flags.skip-ci.summary'), char: 's', default: false, }), 'merge-method': Flags.string({ summary: messages.getMessage('flags.merge-method.summary'), options: ['merge', 'squash', 'rebase'], default: 'merge', }), }; // private props initialized early in run() octokit; baseRepoObject; async run() { const { flags } = await this.parse(AutoMerge); const auth = ensureString(new Env().getString('GH_TOKEN') ?? new Env().getString('GITHUB_TOKEN'), 'GH_TOKEN is required to be set in the environment'); this.baseRepoObject = await getOwnerAndRepo(flags.owner, flags.repo); this.octokit = new Octokit({ auth }); this.log(`owner: ${this.baseRepoObject.owner}, scope: ${this.baseRepoObject.repo}`); const eligiblePRs = (await this.octokit.request('GET /repos/{owner}/{repo}/pulls', this.baseRepoObject)).data.filter((pr) => pr.state === 'open' && (pr.user?.login === 'dependabot[bot]' || (pr.title.includes('refactor: devScripts update') && pr.user?.login === 'svc-cli-bot'))); const greenPRs = (await Promise.all(eligiblePRs.map((pr) => this.isGreen(pr)))).filter(isPrNotUndefined); const mergeablePRs = (await Promise.all(greenPRs.map((pr) => this.isMergeable(pr)))).filter(isPrNotUndefined); this.table({ data: mergeablePRs.map((pr) => ({ 'Green, Mergeable PR': pr.title, Link: pr.html_url })), }); this.log(''); const prToMerge = mergeablePRs[0]; if (!prToMerge) { this.log('No PRs can be automerged'); return; } if (flags.dryrun === false) { this.log(`merging ${prToMerge.number.toString()} | ${prToMerge.title}`); const opts = { ...this.baseRepoObject, // TODO: make oclif smarter about options on flags merge_method: flags['merge-method'], pull_number: prToMerge.number, }; if (flags['skip-ci']) { opts.commit_title = `Merge pull request #${prToMerge.number} from ${prToMerge.head.ref} [skip ci]`; } const mergeResult = await this.octokit.request('PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge', opts); this.styledJSON(mergeResult); } else { this.log(`dry run ${prToMerge.number.toString()} | ${prToMerge.title}`); } } async isGreen(pr) { const statusResponse = await this.octokit.request('GET /repos/{owner}/{repo}/commits/{ref}/status', { ...this.baseRepoObject, ref: pr.head.sha, }); // no point looking at check runs if the commit status is not green if (statusResponse.data.state !== 'success') { return undefined; } const checkRunResponse = await this.octokit.request('GET /repos/{owner}/{repo}/commits/{ref}/check-runs', { ...this.baseRepoObject, ref: pr.head.sha, }); this.styledJSON(checkRunResponse.data); if (checkRunResponse.data.check_runs.every((cr) => cr.status === 'completed' && cr.conclusion && ['success', 'skipped'].includes(cr.conclusion))) { return pr; } } async isMergeable(pr) { const statusResponse = await this.octokit.request('GET /repos/{owner}/{repo}/pulls/{pull_number}', { ...this.baseRepoObject, pull_number: pr.number, }); // mergeable_state of 'blocked' is ok because that's just missing an approval. // We're screening out 'behind' which might be merge conflicts. // Dependabot should rebase this PR eventually if (statusResponse.data.mergeable === true && statusResponse.data.mergeable_state !== 'behind') { return pr; } } } const isPrNotUndefined = (pr) => pr !== undefined; //# sourceMappingURL=automerge.js.map