aboutsummaryrefslogtreecommitdiffstats
path: root/packages/monorepo-scripts/src/utils/utils.ts
blob: f819ab6f11ef317878afd96016d3c6e47aa98d8d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import lernaGetPackages = require('lerna-get-packages');
import * as _ from 'lodash';
import { exec as execAsync } from 'promisify-child-process';
import semver = require('semver');

import { constants } from '../constants';
import { GitTagsByPackageName, UpdatedPackage } from '../types';

import { changelogUtils } from './changelog_utils';

export const utils = {
    log(...args: any[]): void {
        console.log(...args); // tslint:disable-line:no-console
    },
    async getUpdatedLernaPackagesAsync(shouldIncludePrivate: boolean): Promise<LernaPackage[]> {
        const updatedPublicPackages = await this.getLernaUpdatedPackagesAsync(shouldIncludePrivate);
        const updatedPackageNames = _.map(updatedPublicPackages, pkg => pkg.name);

        const allLernaPackages = lernaGetPackages(constants.monorepoRootPath);
        const updatedPublicLernaPackages = _.filter(allLernaPackages, pkg => {
            return _.includes(updatedPackageNames, pkg.package.name);
        });
        return updatedPublicLernaPackages;
    },
    async getLernaUpdatedPackagesAsync(shouldIncludePrivate: boolean): Promise<UpdatedPackage[]> {
        const result = await execAsync(`${constants.lernaExecutable} updated --json`, {
            cwd: constants.monorepoRootPath,
        });
        const updatedPackages = JSON.parse(result.stdout);
        if (!shouldIncludePrivate) {
            const updatedPublicPackages = _.filter(updatedPackages, updatedPackage => !updatedPackage.private);
            return updatedPublicPackages;
        }
        return updatedPackages;
    },
    async getNextPackageVersionAsync(
        currentVersion: string,
        packageName: string,
        packageLocation: string,
    ): Promise<string> {
        let nextVersionIfValid;
        const changelog = changelogUtils.getChangelogOrCreateIfMissing(packageName, packageLocation);
        if (_.isEmpty(changelog)) {
            nextVersionIfValid = semver.inc(currentVersion, 'patch');
        }
        const lastEntry = changelog[0];
        if (semver.gt(currentVersion, lastEntry.version)) {
            throw new Error(`Package.json version cannot be greater then last CHANGELOG entry. Check: ${packageName}`);
        }
        nextVersionIfValid = semver.eq(lastEntry.version, currentVersion)
            ? semver.inc(currentVersion, 'patch')
            : lastEntry.version;
        if (_.isNull(nextVersionIfValid)) {
            throw new Error(`Encountered invalid semver: ${currentVersion} associated with ${packageName}`);
        }
        return nextVersionIfValid;
    },
    async getRemoteGitTagsAsync(): Promise<string[]> {
        const result = await execAsync(`git ls-remote --tags`, {
            cwd: constants.monorepoRootPath,
        });
        const tagsString = result.stdout;
        const tagOutputs: string[] = tagsString.split('\n');
        const tags = _.compact(
            _.map(tagOutputs, tagOutput => {
                const tag = tagOutput.split('refs/tags/')[1];
                // Tags with `^{}` are duplicateous so we ignore them
                // Source: https://stackoverflow.com/questions/15472107/when-listing-git-ls-remote-why-theres-after-the-tag-name
                if (_.endsWith(tag, '^{}')) {
                    return undefined;
                }
                return tag;
            }),
        );
        return tags;
    },
    async getLocalGitTagsAsync(): Promise<string[]> {
        const result = await execAsync(`git tags`, {
            cwd: constants.monorepoRootPath,
        });
        const tagsString = result.stdout;
        const tags = tagsString.split('\n');
        return tags;
    },
    async getGitTagsByPackageNameAsync(packageNames: string[], gitTags: string[]): Promise<GitTagsByPackageName> {
        const tagVersionByPackageName: GitTagsByPackageName = {};
        _.each(gitTags, tag => {
            const packageNameIfExists = _.find(packageNames, name => {
                return _.includes(tag, `${name}@`);
            });
            if (_.isUndefined(packageNameIfExists)) {
                return; // ignore tags not related to a package we care about.
            }
            const splitTag = tag.split(`${packageNameIfExists}@`);
            if (splitTag.length !== 2) {
                throw new Error(`Unexpected tag name found: ${tag}`);
            }
            const version = splitTag[1];
            (tagVersionByPackageName[packageNameIfExists] || (tagVersionByPackageName[packageNameIfExists] = [])).push(
                version,
            );
        });
        return tagVersionByPackageName;
    },
    async removeLocalTagAsync(tagName: string): Promise<void> {
        try {
            await execAsync(`git tag -d ${tagName}`, {
                cwd: constants.monorepoRootPath,
            });
        } catch (err) {
            throw new Error(`Failed to delete local git tag. Got err: ${err}`);
        }
        this.log(`Removed local tag: ${tagName}`);
    },
    async removeRemoteTagAsync(tagName: string): Promise<void> {
        try {
            await execAsync(`git push origin ${tagName}`, {
                cwd: constants.monorepoRootPath,
            });
        } catch (err) {
            throw new Error(`Failed to delete remote git tag. Got err: ${err}`);
        }
        this.log(`Removed remote tag: ${tagName}`);
    },
};