Merge pull request #722 from 0xProject/improvement/publishing-v2
Improvements to pre-publishing checks
This commit is contained in:
@@ -8,8 +8,6 @@ This repository contains a few helpful scripts for working with this mono repo.
|
||||
|
||||
**`yarn find_unused_deps`**: Sometimes we accidentally leave dependencies listed in `package.json` that are no longer being used. This script finds potential dependencies that might no longer be in use. Please verify that it is no longer in use before removing, the `depcheck` package we use under-the-hood doesn't handle some TS quirks perfectly.
|
||||
|
||||
**`yarn remove_tags`**: Our publishing script calls `lerna publish` under-the-hood. If this command fails, it might have created new versioned git tags for each package. Removing these manually is tedious, so you can also run this command instead. Before doing so, check to see if `lerna` already created the publish commit. If so, first revert that with `git reset --hard HEAD~1`, then run this command.
|
||||
|
||||
**`yarn test:publish`**: Execute a test-run of the publish script. This dry run won't actually publish, nor will it commit/push anything to Github.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -14,12 +14,10 @@
|
||||
"clean": "shx rm -rf lib",
|
||||
"test:publish": "run-s build script:publish",
|
||||
"find_unused_deps": "run-s build script:find_unused_deps",
|
||||
"remove_tags": "run-s build script:remove_tags",
|
||||
"script:deps_versions": "node ./lib/deps_versions.js",
|
||||
"script:prepublish_checks": "node ./lib/prepublish_checks.js",
|
||||
"script:publish": "IS_DRY_RUN=true node ./lib/publish.js",
|
||||
"script:find_unused_deps": "node ./lib/find_unused_dependencies.js",
|
||||
"script:remove_tags": "node ./lib/remove_tags.js"
|
||||
"script:find_unused_deps": "node ./lib/find_unused_dependencies.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -40,6 +38,7 @@
|
||||
"make-promises-safe": "^1.1.0",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"shx": "^0.2.2",
|
||||
"@types/semver": "5.5.0",
|
||||
"tslint": "5.8.0",
|
||||
"typescript": "2.7.1"
|
||||
},
|
||||
@@ -50,6 +49,7 @@
|
||||
"es6-promisify": "^5.0.0",
|
||||
"glob": "^7.1.2",
|
||||
"lodash": "^4.17.4",
|
||||
"isomorphic-fetch": "2.2.1",
|
||||
"moment": "2.21.0",
|
||||
"opn": "^5.3.0",
|
||||
"promisify-child-process": "^1.0.5",
|
||||
@@ -57,6 +57,7 @@
|
||||
"publish-release": "0xproject/publish-release",
|
||||
"rimraf": "^2.6.2",
|
||||
"semver-diff": "^2.1.0",
|
||||
"semver": "5.5.0",
|
||||
"semver-sort": "0.0.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,9 +1,128 @@
|
||||
import * as _ from 'lodash';
|
||||
import { exec as execAsync } from 'promisify-child-process';
|
||||
import semver = require('semver');
|
||||
import semverSort = require('semver-sort');
|
||||
|
||||
import { constants } from './constants';
|
||||
import { changelogUtils } from './utils/changelog_utils';
|
||||
import { npmUtils } from './utils/npm_utils';
|
||||
import { utils } from './utils/utils';
|
||||
|
||||
async function prepublishChecksAsync(): Promise<void> {
|
||||
const shouldIncludePrivate = false;
|
||||
const updatedPublicLernaPackages = await utils.getUpdatedLernaPackagesAsync(shouldIncludePrivate);
|
||||
|
||||
await checkCurrentVersionMatchesLatestPublishedNPMPackageAsync(updatedPublicLernaPackages);
|
||||
await checkChangelogFormatAsync(updatedPublicLernaPackages);
|
||||
await checkGitTagsForNextVersionAndDeleteIfExistAsync(updatedPublicLernaPackages);
|
||||
await checkPublishRequiredSetupAsync();
|
||||
}
|
||||
|
||||
async function checkGitTagsForNextVersionAndDeleteIfExistAsync(
|
||||
updatedPublicLernaPackages: LernaPackage[],
|
||||
): Promise<void> {
|
||||
const packageNames = _.map(updatedPublicLernaPackages, lernaPackage => lernaPackage.package.name);
|
||||
const localGitTags = await utils.getLocalGitTagsAsync();
|
||||
const localTagVersionsByPackageName = await utils.getGitTagsByPackageNameAsync(packageNames, localGitTags);
|
||||
|
||||
const remoteGitTags = await utils.getRemoteGitTagsAsync();
|
||||
const remoteTagVersionsByPackageName = await utils.getGitTagsByPackageNameAsync(packageNames, remoteGitTags);
|
||||
|
||||
for (const lernaPackage of updatedPublicLernaPackages) {
|
||||
const currentVersion = lernaPackage.package.version;
|
||||
const packageName = lernaPackage.package.name;
|
||||
const packageLocation = lernaPackage.location;
|
||||
const nextVersion = await utils.getNextPackageVersionAsync(currentVersion, packageName, packageLocation);
|
||||
|
||||
const localTagVersions = localTagVersionsByPackageName[packageName];
|
||||
if (_.includes(localTagVersions, nextVersion)) {
|
||||
const tagName = `${packageName}@${nextVersion}`;
|
||||
await utils.removeLocalTagAsync(tagName);
|
||||
}
|
||||
|
||||
const remoteTagVersions = remoteTagVersionsByPackageName[packageName];
|
||||
if (_.includes(remoteTagVersions, nextVersion)) {
|
||||
const tagName = `:refs/tags/${packageName}@${nextVersion}`;
|
||||
await utils.removeRemoteTagAsync(tagName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function checkCurrentVersionMatchesLatestPublishedNPMPackageAsync(
|
||||
updatedPublicLernaPackages: LernaPackage[],
|
||||
): Promise<void> {
|
||||
utils.log('Check package versions against npmjs.org...');
|
||||
const versionMismatches = [];
|
||||
for (const lernaPackage of updatedPublicLernaPackages) {
|
||||
const packageName = lernaPackage.package.name;
|
||||
const packageVersion = lernaPackage.package.version;
|
||||
const packageRegistryJsonIfExists = await npmUtils.getPackageRegistryJsonIfExistsAsync(packageName);
|
||||
if (_.isUndefined(packageRegistryJsonIfExists)) {
|
||||
continue; // noop for packages not yet published to NPM
|
||||
}
|
||||
const allVersionsIncludingUnpublished = npmUtils.getPreviouslyPublishedVersions(packageRegistryJsonIfExists);
|
||||
const sortedVersions = semverSort.desc(allVersionsIncludingUnpublished);
|
||||
const latestNPMVersion = sortedVersions[0];
|
||||
if (packageVersion !== latestNPMVersion) {
|
||||
versionMismatches.push({
|
||||
packageJsonVersion: packageVersion,
|
||||
npmVersion: latestNPMVersion,
|
||||
packageName,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!_.isEmpty(versionMismatches)) {
|
||||
utils.log(`Found version mismatches between package.json and NPM published versions (might be unpublished).`);
|
||||
_.each(versionMismatches, versionMismatch => {
|
||||
utils.log(
|
||||
`${versionMismatch.packageName}: ${versionMismatch.packageJsonVersion} package.json, ${
|
||||
versionMismatch.npmVersion
|
||||
} on NPM`,
|
||||
);
|
||||
});
|
||||
throw new Error(`Please fix the above package.json/NPM inconsistencies.`);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkChangelogFormatAsync(updatedPublicLernaPackages: LernaPackage[]): Promise<void> {
|
||||
utils.log('Check CHANGELOGs for inconsistencies...');
|
||||
const changeLogInconsistencies = [];
|
||||
for (const lernaPackage of updatedPublicLernaPackages) {
|
||||
const packageName = lernaPackage.package.name;
|
||||
const changelog = changelogUtils.getChangelogOrCreateIfMissing(packageName, lernaPackage.location);
|
||||
|
||||
const currentVersion = lernaPackage.package.version;
|
||||
if (!_.isEmpty(changelog)) {
|
||||
const lastEntry = changelog[0];
|
||||
const doesLastEntryHaveTimestamp = !_.isUndefined(lastEntry.timestamp);
|
||||
if (semver.lt(lastEntry.version, currentVersion)) {
|
||||
changeLogInconsistencies.push({
|
||||
packageJsonVersion: currentVersion,
|
||||
changelogVersion: lastEntry.version,
|
||||
packageName,
|
||||
});
|
||||
} else if (semver.gt(lastEntry.version, currentVersion) && doesLastEntryHaveTimestamp) {
|
||||
// Remove incorrectly added timestamp
|
||||
delete changelog[0].timestamp;
|
||||
// Save updated CHANGELOG.json
|
||||
await changelogUtils.writeChangelogJsonFileAsync(lernaPackage.location, changelog);
|
||||
utils.log(`${packageName}: Removed timestamp from latest CHANGELOG.json entry.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!_.isEmpty(changeLogInconsistencies)) {
|
||||
utils.log(`CHANGELOG versions cannot below package.json versions:`);
|
||||
_.each(changeLogInconsistencies, inconsistency => {
|
||||
utils.log(
|
||||
`${inconsistency.packageName}: ${inconsistency.packageJsonVersion} package.json, ${
|
||||
inconsistency.changelogVersion
|
||||
} CHANGELOG.json`,
|
||||
);
|
||||
});
|
||||
throw new Error('Fix the above inconsistencies to continue.');
|
||||
}
|
||||
}
|
||||
|
||||
async function checkPublishRequiredSetupAsync(): Promise<void> {
|
||||
// check to see if logged into npm before publishing
|
||||
try {
|
||||
@@ -65,7 +184,7 @@ async function checkPublishRequiredSetupAsync(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
checkPublishRequiredSetupAsync().catch(err => {
|
||||
prepublishChecksAsync().catch(err => {
|
||||
utils.log(err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import * as promisify from 'es6-promisify';
|
||||
import * as fs from 'fs';
|
||||
import * as _ from 'lodash';
|
||||
import * as moment from 'moment';
|
||||
import opn = require('opn');
|
||||
import * as path from 'path';
|
||||
import { exec as execAsync, spawn } from 'promisify-child-process';
|
||||
import * as prompt from 'prompt';
|
||||
import semver = require('semver');
|
||||
import semverDiff = require('semver-diff');
|
||||
import semverSort = require('semver-sort');
|
||||
|
||||
import { constants } from './constants';
|
||||
import { Changelog, PackageToVersionChange, SemVerIndex, VersionChangelog } from './types';
|
||||
import { PackageToVersionChange, SemVerIndex, VersionChangelog } from './types';
|
||||
import { changelogUtils } from './utils/changelog_utils';
|
||||
import { utils } from './utils/utils';
|
||||
|
||||
@@ -119,25 +118,23 @@ async function updateChangeLogsAsync(updatedPublicLernaPackages: LernaPackage[])
|
||||
const packageToVersionChange: PackageToVersionChange = {};
|
||||
for (const lernaPackage of updatedPublicLernaPackages) {
|
||||
const packageName = lernaPackage.package.name;
|
||||
const changelogJSONPath = path.join(lernaPackage.location, 'CHANGELOG.json');
|
||||
const changelogJSON = utils.getChangelogJSONOrCreateIfMissing(changelogJSONPath);
|
||||
let changelog: Changelog;
|
||||
try {
|
||||
changelog = JSON.parse(changelogJSON);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`${lernaPackage.package.name}'s CHANGELOG.json contains invalid JSON. Please fix and try again.`,
|
||||
);
|
||||
}
|
||||
let changelog = changelogUtils.getChangelogOrCreateIfMissing(packageName, lernaPackage.location);
|
||||
|
||||
const currentVersion = lernaPackage.package.version;
|
||||
const shouldAddNewEntry = changelogUtils.shouldAddNewChangelogEntry(currentVersion, changelog);
|
||||
const shouldAddNewEntry = changelogUtils.shouldAddNewChangelogEntry(
|
||||
lernaPackage.package.name,
|
||||
currentVersion,
|
||||
changelog,
|
||||
);
|
||||
if (shouldAddNewEntry) {
|
||||
// Create a new entry for a patch version with generic changelog entry.
|
||||
const nextPatchVersion = utils.getNextPatchVersion(currentVersion);
|
||||
const nextPatchVersionIfValid = semver.inc(currentVersion, 'patch');
|
||||
if (_.isNull(nextPatchVersionIfValid)) {
|
||||
throw new Error(`Encountered invalid semver version: ${currentVersion} for package: ${packageName}`);
|
||||
}
|
||||
const newChangelogEntry: VersionChangelog = {
|
||||
timestamp: TODAYS_TIMESTAMP,
|
||||
version: nextPatchVersion,
|
||||
version: nextPatchVersionIfValid,
|
||||
changes: [
|
||||
{
|
||||
note: 'Dependencies updated',
|
||||
@@ -145,7 +142,7 @@ async function updateChangeLogsAsync(updatedPublicLernaPackages: LernaPackage[])
|
||||
],
|
||||
};
|
||||
changelog = [newChangelogEntry, ...changelog];
|
||||
packageToVersionChange[packageName] = semverDiff(currentVersion, nextPatchVersion);
|
||||
packageToVersionChange[packageName] = semverDiff(currentVersion, nextPatchVersionIfValid);
|
||||
} else {
|
||||
// Update existing entry with timestamp
|
||||
const lastEntry = changelog[0];
|
||||
@@ -160,14 +157,11 @@ async function updateChangeLogsAsync(updatedPublicLernaPackages: LernaPackage[])
|
||||
}
|
||||
|
||||
// Save updated CHANGELOG.json
|
||||
fs.writeFileSync(changelogJSONPath, JSON.stringify(changelog, null, '\t'));
|
||||
await utils.prettifyAsync(changelogJSONPath, constants.monorepoRootPath);
|
||||
await changelogUtils.writeChangelogJsonFileAsync(lernaPackage.location, changelog);
|
||||
utils.log(`${packageName}: Updated CHANGELOG.json`);
|
||||
// Generate updated CHANGELOG.md
|
||||
const changelogMd = changelogUtils.generateChangelogMd(changelog);
|
||||
const changelogMdPath = path.join(lernaPackage.location, 'CHANGELOG.md');
|
||||
fs.writeFileSync(changelogMdPath, changelogMd);
|
||||
await utils.prettifyAsync(changelogMdPath, constants.monorepoRootPath);
|
||||
await changelogUtils.writeChangelogMdFileAsync(lernaPackage.location, changelogMd);
|
||||
utils.log(`${packageName}: Updated CHANGELOG.md`);
|
||||
}
|
||||
|
||||
@@ -217,12 +211,16 @@ async function lernaPublishAsync(packageToVersionChange: { [name: string]: strin
|
||||
}
|
||||
|
||||
function updateVersionNumberIfNeeded(currentVersion: string, proposedNextVersion: string): string {
|
||||
const updatedVersionIfValid = semver.inc(currentVersion, 'patch');
|
||||
if (_.isNull(updatedVersionIfValid)) {
|
||||
throw new Error(`Encountered invalid semver: ${currentVersion}`);
|
||||
}
|
||||
if (proposedNextVersion === currentVersion) {
|
||||
return utils.getNextPatchVersion(currentVersion);
|
||||
return updatedVersionIfValid;
|
||||
}
|
||||
const sortedVersions = semverSort.desc([proposedNextVersion, currentVersion]);
|
||||
if (sortedVersions[0] !== proposedNextVersion) {
|
||||
return utils.getNextPatchVersion(currentVersion);
|
||||
return updatedVersionIfValid;
|
||||
}
|
||||
return proposedNextVersion;
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import { exec as execAsync } from 'promisify-child-process';
|
||||
import semverSort = require('semver-sort');
|
||||
|
||||
import { constants } from './constants';
|
||||
import { Changelog } from './types';
|
||||
import { utils } from './utils/utils';
|
||||
|
||||
(async () => {
|
||||
const shouldIncludePrivate = true;
|
||||
const updatedPublicLernaPackages = await utils.getUpdatedLernaPackagesAsync(shouldIncludePrivate);
|
||||
|
||||
for (const lernaPackage of updatedPublicLernaPackages) {
|
||||
const packageName = lernaPackage.package.name;
|
||||
const currentVersion = lernaPackage.package.version;
|
||||
const changelogJSONPath = path.join(lernaPackage.location, 'CHANGELOG.json');
|
||||
// Private packages don't have changelogs, and their versions are always incremented
|
||||
// by a patch version.
|
||||
const changelogJSONIfExists = utils.getChangelogJSONIfExists(changelogJSONPath);
|
||||
|
||||
let latestChangelogVersion: string;
|
||||
if (!_.isUndefined(changelogJSONIfExists)) {
|
||||
let changelogs: Changelog;
|
||||
try {
|
||||
changelogs = JSON.parse(changelogJSONIfExists);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`${lernaPackage.package.name}'s CHANGELOG.json contains invalid JSON. Please fix and try again.`,
|
||||
);
|
||||
}
|
||||
latestChangelogVersion = changelogs[0].version;
|
||||
} else {
|
||||
latestChangelogVersion = utils.getNextPatchVersion(currentVersion);
|
||||
}
|
||||
|
||||
const sortedVersions = semverSort.desc([latestChangelogVersion, currentVersion]);
|
||||
if (sortedVersions[0] === latestChangelogVersion && latestChangelogVersion !== currentVersion) {
|
||||
const tagName = `${packageName}@${latestChangelogVersion}`;
|
||||
try {
|
||||
await execAsync(`git tag -d ${tagName}`, { cwd: constants.monorepoRootPath });
|
||||
utils.log(`removed tag: ${tagName}`);
|
||||
} catch (err) {
|
||||
if (_.includes(err.message, 'not found')) {
|
||||
utils.log(`Could not find tag: ${tagName}`);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})().catch(err => {
|
||||
utils.log(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -27,3 +27,16 @@ export enum SemVerIndex {
|
||||
export interface PackageToVersionChange {
|
||||
[name: string]: string;
|
||||
}
|
||||
|
||||
export interface PackageRegistryJson {
|
||||
versions: {
|
||||
[version: string]: any;
|
||||
};
|
||||
time: {
|
||||
[version: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GitTagsByPackageName {
|
||||
[packageName: string]: string[];
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
28
packages/monorepo-scripts/src/utils/npm_utils.ts
Normal file
28
packages/monorepo-scripts/src/utils/npm_utils.ts
Normal file
@@ -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;
|
||||
},
|
||||
};
|
||||
@@ -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;
|
||||
},
|
||||
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}`);
|
||||
}
|
||||
},
|
||||
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 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;
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user