From 19668b9b48eb08645f500dd8453b8cb2f7abc400 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 18 Jun 2018 16:46:54 +0200 Subject: remove remove_tags script --- packages/monorepo-scripts/README.md | 2 -- packages/monorepo-scripts/package.json | 2 -- 2 files changed, 4 deletions(-) diff --git a/packages/monorepo-scripts/README.md b/packages/monorepo-scripts/README.md index 22b449870..d979e27dc 100644 --- a/packages/monorepo-scripts/README.md +++ b/packages/monorepo-scripts/README.md @@ -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 diff --git a/packages/monorepo-scripts/package.json b/packages/monorepo-scripts/package.json index 5a6d7b25a..9a466ffd0 100644 --- a/packages/monorepo-scripts/package.json +++ b/packages/monorepo-scripts/package.json @@ -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" }, "repository": { "type": "git", -- cgit From 8633fa702436cceeafa52ec39a7fabb5b2650c38 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 18 Jun 2018 16:55:59 +0200 Subject: Add more prepublish checks --- packages/monorepo-scripts/package.json | 1 + packages/monorepo-scripts/src/prepublish_checks.ts | 96 ++++++++++++++++++++- packages/monorepo-scripts/src/publish.ts | 24 ++---- packages/monorepo-scripts/src/types.ts | 13 +++ .../monorepo-scripts/src/utils/changelog_utils.ts | 55 +++++++++++- packages/monorepo-scripts/src/utils/npm_utils.ts | 28 +++++++ .../monorepo-scripts/src/utils/semver_utils.ts | 56 +++++++++++++ packages/monorepo-scripts/src/utils/utils.ts | 98 +++++++++++++++++----- 8 files changed, 332 insertions(+), 39 deletions(-) create mode 100644 packages/monorepo-scripts/src/utils/npm_utils.ts create mode 100644 packages/monorepo-scripts/src/utils/semver_utils.ts diff --git a/packages/monorepo-scripts/package.json b/packages/monorepo-scripts/package.json index 9a466ffd0..fe9194b2f 100644 --- a/packages/monorepo-scripts/package.json +++ b/packages/monorepo-scripts/package.json @@ -48,6 +48,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", diff --git a/packages/monorepo-scripts/src/prepublish_checks.ts b/packages/monorepo-scripts/src/prepublish_checks.ts index 2c096d8f6..64de56ece 100644 --- a/packages/monorepo-scripts/src/prepublish_checks.ts +++ b/packages/monorepo-scripts/src/prepublish_checks.ts @@ -1,9 +1,103 @@ +import * as fs from 'fs'; import * as _ from 'lodash'; +import * as path from 'path'; import { exec as execAsync } from 'promisify-child-process'; import { constants } from './constants'; +import { Changelog, PackageRegistryJson } from './types'; +import { changelogUtils } from './utils/changelog_utils'; +import { npmUtils } from './utils/npm_utils'; +import { semverUtils } from './utils/semver_utils'; import { utils } from './utils/utils'; +async function prepublishChecksAsync(): Promise { + 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 { + 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 { + 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 latestNPMVersion = semverUtils.getLatestVersion(allVersionsIncludingUnpublished); + if (packageVersion !== latestNPMVersion) { + throw new Error( + `Found verson ${packageVersion} in package.json but version ${latestNPMVersion} + on NPM (could be unpublished version) for ${packageName}. These versions must match. If you update + the package.json version, make sure to also update the internal dependency versions too.`, + ); + } + } +} + +async function checkChangelogFormatAsync(updatedPublicLernaPackages: LernaPackage[]): Promise { + 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 (semverUtils.lessThan(lastEntry.version, currentVersion)) { + throw new Error( + `CHANGELOG version cannot be below current package version. + Update ${packageName}'s CHANGELOG. It's current version is ${currentVersion} + but the latest CHANGELOG entry is: ${lastEntry.version}`, + ); + } else if (semverUtils.greaterThan(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.`); + } + } + } +} + async function checkPublishRequiredSetupAsync(): Promise { // check to see if logged into npm before publishing try { @@ -65,7 +159,7 @@ async function checkPublishRequiredSetupAsync(): Promise { } } -checkPublishRequiredSetupAsync().catch(err => { +prepublishChecksAsync().catch(err => { utils.log(err.message); process.exit(1); }); diff --git a/packages/monorepo-scripts/src/publish.ts b/packages/monorepo-scripts/src/publish.ts index 2efbc8bf2..637512a5a 100644 --- a/packages/monorepo-scripts/src/publish.ts +++ b/packages/monorepo-scripts/src/publish.ts @@ -119,19 +119,14 @@ 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); @@ -160,14 +155,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, changelog); utils.log(`${packageName}: Updated CHANGELOG.md`); } diff --git a/packages/monorepo-scripts/src/types.ts b/packages/monorepo-scripts/src/types.ts index 36fb923b3..61bd75732 100644 --- a/packages/monorepo-scripts/src/types.ts +++ b/packages/monorepo-scripts/src/types.ts @@ -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[]; +} 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 = `