1import chalk from 'chalk';
2import inquirer from 'inquirer';
3
4import Git from '../../Git';
5import logger from '../../Logger';
6import { Task } from '../../TasksRunner';
7import { CommandOptions, Parcel, TaskArgs } from '../types';
8
9const { cyan, yellow, blue } = chalk;
10
11/**
12 * Checks whether the current branch is correct and working dir is not dirty.
13 */
14export const checkRepositoryStatus = new Task<TaskArgs>(
15  {
16    name: 'checkRepositoryStatus',
17    required: true,
18    backupable: false,
19  },
20  async (parcels: Parcel[], options: CommandOptions): Promise<void | symbol> => {
21    if (options.skipRepoChecks) {
22      return;
23    }
24    logger.info(`\n��️‍♂️ Checking repository status...`);
25
26    const currentBranch = await Git.getCurrentBranchNameAsync();
27    const trackingBranch = await Git.getTrackingBranchNameAsync();
28
29    // Check whether it's allowed to publish from the current branch.
30    if (!(await checkBranchNameAsync(currentBranch))) {
31      return Task.STOP;
32    }
33
34    // If tracking branch is set, then we must ensure it is still up-to-date with it.
35    if (trackingBranch) {
36      await Git.fetchAsync();
37
38      const stats = await Git.compareBranchesAsync(currentBranch, trackingBranch);
39
40      if (stats.ahead + stats.behind > 0) {
41        logger.error(
42          `�� Your local branch ${cyan(currentBranch)} is out of sync with remote branch.`
43        );
44        return Task.STOP;
45      }
46    }
47    if (await Git.hasUnstagedChangesAsync()) {
48      logger.error(`�� Repository contains unstaged changes, please make sure to have it clear.`);
49      logger.error(`�� If you want to include them, they must be committed.`);
50      return Task.STOP;
51    }
52  }
53);
54
55/**
56 * Checks whether the command is run on main branch or package side-branch.
57 * Otherwise, it prompts to confirm that you know what you're doing.
58 * On CI it returns `true` only if run on `main` branch.
59 */
60async function checkBranchNameAsync(branchName: string) {
61  if (process.env.CI) {
62    // CI is allowed to publish only from main.
63    return branchName === 'main';
64  }
65
66  // Publishes can be run on `main` or package's side-branches like `expo-package/1.x.x`
67  if (branchName === 'main' || /^[\w\-@]+\/\d+\.(x\.x|\d+\.x)$/.test(branchName)) {
68    return true;
69  }
70
71  logger.warn(
72    '⚠️ ',
73    `It's recommended to publish from ${blue('main')} branch, while you're at ${blue(branchName)}`
74  );
75
76  const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>([
77    {
78      type: 'confirm',
79      name: 'confirmed',
80      prefix: yellow('⚠️ '),
81      message: yellow(`Do you want to proceed?`),
82      default: true,
83    },
84  ]);
85  logger.log();
86  return confirmed;
87}
88