diff options
Diffstat (limited to 'packages/monorepo-scripts/src/utils')
-rw-r--r-- | packages/monorepo-scripts/src/utils/changelog_utils.ts | 53 | ||||
-rw-r--r-- | packages/monorepo-scripts/src/utils/npm_utils.ts | 28 | ||||
-rw-r--r-- | packages/monorepo-scripts/src/utils/utils.ts | 110 |
3 files changed, 162 insertions, 29 deletions
diff --git a/packages/monorepo-scripts/src/utils/changelog_utils.ts b/packages/monorepo-scripts/src/utils/changelog_utils.ts index edfe65a80..dbafb6f16 100644 --- a/packages/monorepo-scripts/src/utils/changelog_utils.ts +++ b/packages/monorepo-scripts/src/utils/changelog_utils.ts @@ -1,6 +1,11 @@ +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 semver = require('semver'); +import { constants } from '../constants'; import { Change, Changelog, VersionChangelog } from '../types'; const CHANGELOG_MD_HEADER = ` @@ -44,12 +49,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 (semver.lt(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, changelogMdString: string): Promise<void> { + const changelogMarkdownPath = path.join(packageLocation, 'CHANGELOG.md'); + fs.writeFileSync(changelogMarkdownPath, changelogMdString); + 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/utils.ts b/packages/monorepo-scripts/src/utils/utils.ts index 0b8ac4c0b..20bc57bae 100644 --- a/packages/monorepo-scripts/src/utils/utils.ts +++ b/packages/monorepo-scripts/src/utils/utils.ts @@ -1,27 +1,17 @@ -import * as fs from 'fs'; 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 { UpdatedPackage } from '../types'; +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 }, - getNextPatchVersion(currentVersion: string): string { - const versionSegments = currentVersion.split('.'); - const patch = _.parseInt(_.last(versionSegments) as string); - const newPatch = patch + 1; - 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 +33,86 @@ 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 nextVersionIfValid; + const changelog = changelogUtils.getChangelogOrCreateIfMissing(packageName, packageLocation); + if (_.isEmpty(changelog)) { + nextVersionIfValid = semver.inc(currentVersion, 'patch'); + } + const lastEntry = changelog[0]; + 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; }, - 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 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> { + 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; }, }; |