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}`);
},
};
|