aboutsummaryrefslogtreecommitdiffstats
path: root/packages/monorepo-scripts/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'packages/monorepo-scripts/src/utils')
-rw-r--r--packages/monorepo-scripts/src/utils/changelog_utils.ts55
-rw-r--r--packages/monorepo-scripts/src/utils/npm_utils.ts28
-rw-r--r--packages/monorepo-scripts/src/utils/semver_utils.ts56
-rw-r--r--packages/monorepo-scripts/src/utils/utils.ts98
4 files changed, 215 insertions, 22 deletions
diff --git a/packages/monorepo-scripts/src/utils/changelog_utils.ts b/packages/monorepo-scripts/src/utils/changelog_utils.ts
index edfe65a80..4e09fc842 100644
--- a/packages/monorepo-scripts/src/utils/changelog_utils.ts
+++ b/packages/monorepo-scripts/src/utils/changelog_utils.ts
@@ -1,8 +1,15 @@
+import * as fs from 'fs';
import * as _ from 'lodash';
import * as moment from 'moment';
+import * as path from 'path';
+import { exec as execAsync } from 'promisify-child-process';
+import semverSort = require('semver-sort');
+import { constants } from '../constants';
import { Change, Changelog, VersionChangelog } from '../types';
+import { semverUtils } from './semver_utils';
+
const CHANGELOG_MD_HEADER = `
<!--
This file is auto-generated using the monorepo-scripts package. Don't edit directly.
@@ -44,12 +51,58 @@ export const changelogUtils = {
return changelogMd;
},
- shouldAddNewChangelogEntry(currentVersion: string, changelog: Changelog): boolean {
+ shouldAddNewChangelogEntry(packageName: string, currentVersion: string, changelog: Changelog): boolean {
if (_.isEmpty(changelog)) {
return true;
}
const lastEntry = changelog[0];
+ if (semverUtils.lessThan(lastEntry.version, currentVersion)) {
+ throw new Error(
+ `Found CHANGELOG version lower then current package version. ${packageName} current: ${currentVersion}, Changelog: ${
+ lastEntry.version
+ }`,
+ );
+ }
const isLastEntryCurrentVersion = lastEntry.version === currentVersion;
return isLastEntryCurrentVersion;
},
+ getChangelogJSONIfExists(changelogPath: string): string | undefined {
+ try {
+ const changelogJSON = fs.readFileSync(changelogPath, 'utf-8');
+ return changelogJSON;
+ } catch (err) {
+ return undefined;
+ }
+ },
+ getChangelogOrCreateIfMissing(packageName: string, packageLocation: string): Changelog {
+ const changelogJSONPath = path.join(packageLocation, 'CHANGELOG.json');
+ let changelogJsonIfExists = this.getChangelogJSONIfExists(changelogJSONPath);
+ if (_.isUndefined(changelogJsonIfExists)) {
+ // If none exists, create new, empty one.
+ changelogJsonIfExists = '[]';
+ fs.writeFileSync(changelogJSONPath, changelogJsonIfExists);
+ }
+ let changelog: Changelog;
+ try {
+ changelog = JSON.parse(changelogJsonIfExists);
+ } catch (err) {
+ throw new Error(`${packageName}'s CHANGELOG.json contains invalid JSON. Please fix and try again.`);
+ }
+ return changelog;
+ },
+ async writeChangelogJsonFileAsync(packageLocation: string, changelog: Changelog): Promise<void> {
+ const changelogJSONPath = path.join(packageLocation, 'CHANGELOG.json');
+ fs.writeFileSync(changelogJSONPath, JSON.stringify(changelog, null, '\t'));
+ await this.prettifyAsync(changelogJSONPath, constants.monorepoRootPath);
+ },
+ async writeChangelogMdFileAsync(packageLocation: string, changelog: Changelog): Promise<void> {
+ const changelogMarkdownPath = path.join(packageLocation, 'CHANGELOG.md');
+ fs.writeFileSync(changelogMarkdownPath, JSON.stringify(changelog, null, '\t'));
+ await this.prettifyAsync(changelogMarkdownPath, constants.monorepoRootPath);
+ },
+ async prettifyAsync(filePath: string, cwd: string): Promise<void> {
+ await execAsync(`prettier --write ${filePath} --config .prettierrc`, {
+ cwd,
+ });
+ },
};
diff --git a/packages/monorepo-scripts/src/utils/npm_utils.ts b/packages/monorepo-scripts/src/utils/npm_utils.ts
new file mode 100644
index 000000000..cc1e046e7
--- /dev/null
+++ b/packages/monorepo-scripts/src/utils/npm_utils.ts
@@ -0,0 +1,28 @@
+import 'isomorphic-fetch';
+import * as _ from 'lodash';
+
+import { PackageRegistryJson } from '../types';
+
+const NPM_REGISTRY_BASE_URL = 'https://registry.npmjs.org';
+const SUCCESS_STATUS = 200;
+const NOT_FOUND_STATUS = 404;
+
+export const npmUtils = {
+ async getPackageRegistryJsonIfExistsAsync(packageName: string): Promise<PackageRegistryJson | undefined> {
+ const url = `${NPM_REGISTRY_BASE_URL}/${packageName}`;
+ const response = await fetch(url);
+
+ if (response.status === NOT_FOUND_STATUS) {
+ return undefined;
+ } else if (response.status !== SUCCESS_STATUS) {
+ throw new Error(`Request to ${url} failed. Check your internet connection and that npmjs.org is up.`);
+ }
+ const packageRegistryJson = await response.json();
+ return packageRegistryJson;
+ },
+ getPreviouslyPublishedVersions(packageRegistryJson: PackageRegistryJson): string[] {
+ const timeWithOnlyVersions = _.omit(packageRegistryJson.time, ['modified', 'created']);
+ const versions = _.keys(timeWithOnlyVersions);
+ return versions;
+ },
+};
diff --git a/packages/monorepo-scripts/src/utils/semver_utils.ts b/packages/monorepo-scripts/src/utils/semver_utils.ts
new file mode 100644
index 000000000..d5c6b2d17
--- /dev/null
+++ b/packages/monorepo-scripts/src/utils/semver_utils.ts
@@ -0,0 +1,56 @@
+import * as _ from 'lodash';
+import semverSort = require('semver-sort');
+
+// Regex that matches semantic versions only including digits and dots.
+const SEM_VER_REGEX = /^(\d+\.){1}(\d+\.){1}(\d+){1}$/gm;
+
+export const semverUtils = {
+ /**
+ * Checks whether version a is lessThan version b. Supplied versions must be
+ * Semantic Versions containing only numbers and dots (e.g 1.4.0).
+ * @param a version of interest
+ * @param b version to compare a against
+ * @return Whether version a is lessThan version b
+ */
+ lessThan(a: string, b: string): boolean {
+ this.assertValidSemVer('a', a);
+ this.assertValidSemVer('b', b);
+ if (a === b) {
+ return false;
+ }
+ const sortedVersions = semverSort.desc([a, b]);
+ const isALessThanB = sortedVersions[0] === b;
+ return isALessThanB;
+ },
+ /**
+ * Checks whether version a is greaterThan version b. Supplied versions must be
+ * Semantic Versions containing only numbers and dots (e.g 1.4.0).
+ * @param a version of interest
+ * @param b version to compare a against
+ * @return Whether version a is greaterThan version b
+ */
+ greaterThan(a: string, b: string): boolean {
+ this.assertValidSemVer('a', a);
+ this.assertValidSemVer('b', b);
+ if (a === b) {
+ return false;
+ }
+ const sortedVersions = semverSort.desc([a, b]);
+ const isAGreaterThanB = sortedVersions[0] === a;
+ return isAGreaterThanB;
+ },
+ assertValidSemVer(variableName: string, version: string): void {
+ if (!version.match(SEM_VER_REGEX)) {
+ throw new Error(
+ `SemVer versions should only contain numbers and dots. Encountered: ${variableName} = ${version}`,
+ );
+ }
+ },
+ getLatestVersion(versions: string[]): string {
+ _.each(versions, version => {
+ this.assertValidSemVer('version', version);
+ });
+ const sortedVersions = semverSort.desc(versions);
+ return sortedVersions[0];
+ },
+};
diff --git a/packages/monorepo-scripts/src/utils/utils.ts b/packages/monorepo-scripts/src/utils/utils.ts
index 0b8ac4c0b..93de0d940 100644
--- a/packages/monorepo-scripts/src/utils/utils.ts
+++ b/packages/monorepo-scripts/src/utils/utils.ts
@@ -1,10 +1,11 @@
-import * as fs from 'fs';
import lernaGetPackages = require('lerna-get-packages');
import * as _ from 'lodash';
import { exec as execAsync } from 'promisify-child-process';
import { constants } from '../constants';
-import { UpdatedPackage } from '../types';
+import { GitTagsByPackageName, UpdatedPackage } from '../types';
+
+import { changelogUtils } from './changelog_utils';
export const utils = {
log(...args: any[]): void {
@@ -17,11 +18,6 @@ export const utils = {
const newPatchVersion = `${versionSegments[0]}.${versionSegments[1]}.${newPatch}`;
return newPatchVersion;
},
- async prettifyAsync(filePath: string, cwd: string): Promise<void> {
- await execAsync(`prettier --write ${filePath} --config .prettierrc`, {
- cwd,
- });
- },
async getUpdatedLernaPackagesAsync(shouldIncludePrivate: boolean): Promise<LernaPackage[]> {
const updatedPublicPackages = await this.getLernaUpdatedPackagesAsync(shouldIncludePrivate);
const updatedPackageNames = _.map(updatedPublicPackages, pkg => pkg.name);
@@ -43,22 +39,82 @@ export const utils = {
}
return updatedPackages;
},
- getChangelogJSONIfExists(changelogPath: string): string | undefined {
- try {
- const changelogJSON = fs.readFileSync(changelogPath, 'utf-8');
- return changelogJSON;
- } catch (err) {
- return undefined;
+ async getNextPackageVersionAsync(
+ currentVersion: string,
+ packageName: string,
+ packageLocation: string,
+ ): Promise<string> {
+ let nextVersion;
+ const changelog = changelogUtils.getChangelogOrCreateIfMissing(packageName, packageLocation);
+ if (_.isEmpty(changelog)) {
+ nextVersion = this.getNextPatchVersion(currentVersion);
}
+ const lastEntry = changelog[0];
+ nextVersion =
+ lastEntry.version === currentVersion ? this.getNextPatchVersion(currentVersion) : lastEntry.version;
+ return nextVersion;
+ },
+ 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;
},
- getChangelogJSONOrCreateIfMissing(changelogPath: string): string {
- const changelogIfExists = this.getChangelogJSONIfExists(changelogPath);
- if (_.isUndefined(changelogIfExists)) {
- // If none exists, create new, empty one.
- const emptyChangelogJSON = JSON.stringify([]);
- fs.writeFileSync(changelogPath, emptyChangelogJSON);
- return emptyChangelogJSON;
+ 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> {
+ const result = await execAsync(`git tag -d ${tagName}`, {
+ cwd: constants.monorepoRootPath,
+ });
+ if (!_.isEmpty(result.stderr)) {
+ throw new Error(`Failed to delete local git tag. Got err: ${result.stderr}`);
+ }
+ },
+ async removeRemoteTagAsync(tagName: string): Promise<void> {
+ const result = await execAsync(`git push origin ${tagName}`, {
+ cwd: constants.monorepoRootPath,
+ });
+ if (!_.isEmpty(result.stderr)) {
+ throw new Error(`Failed to delete remote git tag. Got err: ${result.stderr}`);
}
- return changelogIfExists;
},
};