1import chalk from 'chalk'; 2import inquirer from 'inquirer'; 3 4import { UNPUBLISHED_VERSION_NAME } from '../../Changelogs'; 5import { link } from '../../Formatter'; 6import * as GitHub from '../../GitHub'; 7import logger from '../../Logger'; 8import { Task } from '../../TasksRunner'; 9import { runWithSpinner } from '../../Utils'; 10import { Parcel, TaskArgs } from '../types'; 11import { selectPackagesToPublish } from './selectPackagesToPublish'; 12 13// https://github.com/expo/expo/pulls?q=label:published 14const PUBLISHED_LABEL_NAME = 'published'; 15 16const { green, blue, magenta, bold } = chalk; 17 18/** 19 * Adds "published" label to pull requests mentioned in changelog entries. 20 */ 21export const addPublishedLabelToPullRequests = new Task<TaskArgs>( 22 { 23 name: 'addPublishedLabelToPullRequests', 24 dependsOn: [selectPackagesToPublish], 25 }, 26 async (parcels: Parcel[]) => { 27 if (!process.env.GITHUB_TOKEN) { 28 logger.error( 29 'Environment variable `%s` must be set to add labels to pull requests', 30 magenta('GITHUB_TOKEN') 31 ); 32 return; 33 } 34 35 // A set of pull request IDs extracted from changelog entries 36 const pullRequestIds = new Set<number>(); 37 38 // Find all pull requests mentioned in changelogs 39 for (const { changelogChanges } of parcels) { 40 const versionChanges = changelogChanges.versions[UNPUBLISHED_VERSION_NAME]; 41 42 if (!versionChanges) { 43 continue; 44 } 45 for (const entry of Object.values(versionChanges).flat()) { 46 entry.pullRequests?.forEach((pullRequestId) => { 47 pullRequestIds.add(pullRequestId); 48 }); 49 } 50 } 51 52 if (pullRequestIds.size === 0) { 53 return; 54 } 55 56 // Request for pull request objects for the extracted IDs 57 // This needs to happen consecutively to reduce the risk of being rate-limited by GitHub 58 const pullRequests = await runWithSpinner( 59 'Requesting published pull requests from GitHub', 60 async () => { 61 const pullRequests: GitHub.PullRequest[] = []; 62 63 for (const pullRequestId of pullRequestIds) { 64 const pullRequest = await GitHub.getPullRequestAsync(pullRequestId, true); 65 const hasLabel = pullRequest.labels.some((label) => label.name === PUBLISHED_LABEL_NAME); 66 67 if (!hasLabel) { 68 pullRequests.push(pullRequest); 69 } 70 } 71 return pullRequests; 72 }, 73 'Loaded published pull requests from GitHub' 74 ); 75 76 // Skip the rest if all pull requests already have the label 77 if (pullRequests.length === 0) { 78 logger.log('There are no pull requests that are not labeled already'); 79 return; 80 } 81 82 // Select pull requests to mark as published 83 const pullRequestsToLabel = await selectPullRequestsToLabel(pullRequests); 84 85 // Finally, consecutively add the label to each pull request 86 await runWithSpinner( 87 'Adding the label to selected pull requests', 88 async () => { 89 for (const pullRequest of pullRequestsToLabel) { 90 await GitHub.addIssueLabelsAsync(pullRequest.number, [PUBLISHED_LABEL_NAME]); 91 } 92 }, 93 'Added the published label' 94 ); 95 96 logger.log(); 97 } 98); 99 100function linkToPullRequest(pr: GitHub.PullRequest): string { 101 return link(blue('#' + pr.number), pr.html_url); 102} 103 104function linkToAuthor(pr: GitHub.PullRequest): string { 105 const { user } = pr; 106 return user ? link(green('@' + user.login), user.html_url) : 'anonymous'; 107} 108 109function formatPullRequest(pr: GitHub.PullRequest): string { 110 return `${linkToPullRequest(pr)}: ${bold(pr.title)} (by ${linkToAuthor(pr)})`; 111} 112 113/** 114 * Prompts the user to select pull requests that should be labeled as published. 115 */ 116async function selectPullRequestsToLabel( 117 pullRequests: GitHub.PullRequest[] 118): Promise<GitHub.PullRequest[]> { 119 const { selectedPullRequests } = await inquirer.prompt([ 120 { 121 type: 'checkbox', 122 name: 'selectedPullRequests', 123 message: 'Which pull requests do you want to label as published?\n', 124 choices: pullRequests.map((pr) => { 125 return { 126 name: formatPullRequest(pr), 127 value: pr, 128 checked: true, 129 }; 130 }), 131 }, 132 ]); 133 return selectedPullRequests as GitHub.PullRequest[]; 134} 135