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
16/**
17 * Adds "published" label to pull requests mentioned in changelog entries.
18 */
19export const addPublishedLabelToPullRequests = new Task<TaskArgs>(
20  {
21    name: 'addPublishedLabelToPullRequests',
22    dependsOn: [selectPackagesToPublish],
23  },
24  async (parcels: Parcel[]) => {
25    if (!process.env.GITHUB_TOKEN) {
26      logger.error(
27        'Environment variable `%s` must be set to add labels to pull requests',
28        chalk.magenta('GITHUB_TOKEN')
29      );
30      return;
31    }
32
33    const pullRequestIds: number[] = [];
34
35    // Find all pull requests mentioned in changelogs
36    for (const { state } of parcels) {
37      const versionChanges = state.changelogChanges?.versions[UNPUBLISHED_VERSION_NAME];
38
39      if (!versionChanges) {
40        continue;
41      }
42      for (const entry of Object.values(versionChanges).flat()) {
43        const { pullRequests } = entry;
44        if (pullRequests && pullRequests.length > 0) {
45          pullRequestIds.push(...pullRequests);
46        }
47      }
48    }
49
50    if (pullRequestIds.length === 0) {
51      return;
52    }
53    const pullRequestIdsSet = new Set<number>(pullRequestIds);
54    const pullRequestsToLabel: GitHub.PullRequest[] = [];
55
56    logger.info(`\n�� List of published pull requests (${pullRequestIdsSet.size}):`);
57
58    // Request and log all published pull requests
59    for (const pullRequestId of pullRequestIdsSet) {
60      const pr = await GitHub.getPullRequestAsync(pullRequestId, true);
61
62      logger.log(`${linkToPullRequest(pr)}: ${chalk.bold(pr.title)}`);
63      pullRequestsToLabel.push(pr);
64    }
65
66    // Ask whether to continue adding the label to pull requests logged above
67    if (!(await shouldLabelPullRequestsAsync())) {
68      return;
69    }
70
71    await runWithSpinner('Adding the label...', async (spinner) => {
72      // Finally add the label to each pull request
73      for (const pullRequest of pullRequestsToLabel) {
74        const hasLabel = pullRequest.labels.some((label) => label.name === PUBLISHED_LABEL_NAME);
75
76        if (!hasLabel) {
77          await GitHub.addIssueLabelsAsync(pullRequest.number, [PUBLISHED_LABEL_NAME]);
78        }
79      }
80      spinner.succeed('Added the published label');
81    });
82
83    logger.log();
84  }
85);
86
87function linkToPullRequest(pr: GitHub.PullRequest): string {
88  return link(chalk.blue('#' + pr.number), pr.html_url);
89}
90
91/**
92 * Prompts the user whether to add the label to pull requests.
93 */
94async function shouldLabelPullRequestsAsync(): Promise<boolean> {
95  if (process.env.CI) {
96    return true;
97  }
98  const { proceed } = await inquirer.prompt<{ proceed: boolean }>([
99    {
100      type: 'confirm',
101      name: 'proceed',
102      prefix: '❔',
103      message: chalk.yellow(
104        `Do you want to add '${chalk.magenta(PUBLISHED_LABEL_NAME)}' label to these pull requests?`
105      ),
106      default: true,
107    },
108  ]);
109  return proceed;
110}
111