From d502f793032d71156739b07b8d322a058c4aba62 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Fri, 30 Mar 2018 14:29:30 +0200 Subject: Rename prepublish to publish now that it goes straight to actually publishing the packages --- packages/monorepo-scripts/package.json | 2 +- packages/monorepo-scripts/src/custom_prepublish.ts | 197 --------------------- packages/monorepo-scripts/src/publish.ts | 197 +++++++++++++++++++++ 3 files changed, 198 insertions(+), 198 deletions(-) delete mode 100644 packages/monorepo-scripts/src/custom_prepublish.ts create mode 100644 packages/monorepo-scripts/src/publish.ts diff --git a/packages/monorepo-scripts/package.json b/packages/monorepo-scripts/package.json index e8c7974e3..7c87199fc 100644 --- a/packages/monorepo-scripts/package.json +++ b/packages/monorepo-scripts/package.json @@ -7,7 +7,7 @@ "scripts": { "build:watch": "tsc -w", "deps_versions": "node ./lib/deps_versions.js", - "publish:prepublish": "yarn build; node ./lib/custom_prepublish.js", + "publish": "yarn build; node ./lib/publish.js", "convert_changelogs": "yarn build; node ./lib/convert_changelogs.js", "lint": "tslint --project . 'src/**/*.ts'", "clean": "shx rm -rf lib", diff --git a/packages/monorepo-scripts/src/custom_prepublish.ts b/packages/monorepo-scripts/src/custom_prepublish.ts deleted file mode 100644 index 4265ee9e2..000000000 --- a/packages/monorepo-scripts/src/custom_prepublish.ts +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env node - -import * as fs from 'fs'; -import lernaGetPackages = require('lerna-get-packages'); -import * as _ from 'lodash'; -import * as moment from 'moment'; -import * as path from 'path'; -import { exec as execAsync, spawn } from 'promisify-child-process'; -import semverDiff = require('semver-diff'); -import semverSort = require('semver-sort'); - -import { Changelog, Changes, SemVerIndex, UpdatedPackage } from './types'; -import { utils } from './utils'; - -const IS_DRY_RUN = true; -const MONOREPO_ROOT_PATH = path.join(__dirname, '../../..'); -const TODAYS_TIMESTAMP = moment().unix(); -const LERNA_EXECUTABLE = './node_modules/lerna/bin/lerna.js'; -const semverNameToIndex: { [semver: string]: number } = { - patch: SemVerIndex.Patch, - minor: SemVerIndex.Minor, - major: SemVerIndex.Major, -}; - -(async () => { - const updatedPublicPackages = await getPublicLernaUpdatedPackagesAsync(); - const updatedPackageNames = _.map(updatedPublicPackages, pkg => pkg.name); - - const allLernaPackages = lernaGetPackages(MONOREPO_ROOT_PATH); - const relevantLernaPackages = _.filter(allLernaPackages, pkg => { - return _.includes(updatedPackageNames, pkg.package.name); - }); - const relevantPackageNames = _.map(relevantLernaPackages, pkg => pkg.package.name); - utils.log(`Will update CHANGELOGs and publish: \n${relevantPackageNames.join('\n')}\n`); - - const packageToVersionChange: { [name: string]: string } = {}; - _.each(relevantLernaPackages, lernaPackage => { - const packageName = lernaPackage.package.name; - const changelogJSONPath = path.join(lernaPackage.location, 'CHANGELOG.json'); - const changelogJSON = getChangelogJSONOrCreateIfMissing(lernaPackage.package.name, changelogJSONPath); - let changelogs: Changelog[]; - try { - changelogs = JSON.parse(changelogJSON); - } catch (err) { - throw new Error( - `${lernaPackage.package.name}'s CHANGELOG.json contains invalid JSON. Please fix and try again.`, - ); - } - - const currentVersion = lernaPackage.package.version; - const shouldAddNewEntry = shouldAddNewChangelogEntry(changelogs); - if (shouldAddNewEntry) { - // Create a new entry for a patch version with generic changelog entry. - const nextPatchVersion = utils.getNextPatchVersion(currentVersion); - const newChangelogEntry: Changelog = { - timestamp: TODAYS_TIMESTAMP, - version: nextPatchVersion, - changes: [ - { - note: 'Dependencies updated', - }, - ], - }; - changelogs = [newChangelogEntry, ...changelogs]; - packageToVersionChange[packageName] = semverDiff(currentVersion, nextPatchVersion); - } else { - // Update existing entry with timestamp - const lastEntry = changelogs[0]; - if (_.isUndefined(lastEntry.timestamp)) { - lastEntry.timestamp = TODAYS_TIMESTAMP; - } - // Check version number is correct. - const proposedNextVersion = lastEntry.version; - lastEntry.version = updateVersionNumberIfNeeded(currentVersion, proposedNextVersion); - changelogs[0] = lastEntry; - packageToVersionChange[packageName] = semverDiff(currentVersion, lastEntry.version); - } - - // Save updated CHANGELOG.json - fs.writeFileSync(changelogJSONPath, JSON.stringify(changelogs, null, '\t')); - utils.log(`${packageName}: Updated CHANGELOG.json`); - // Generate updated CHANGELOG.md - const changelogMd = generateChangelogMd(changelogs); - const changelogMdPath = path.join(lernaPackage.location, 'CHANGELOG.md'); - fs.writeFileSync(changelogMdPath, changelogMd); - utils.log(`${packageName}: Updated CHANGELOG.md`); - }); - - if (!IS_DRY_RUN) { - await execAsync(`git add . --all`, { cwd: MONOREPO_ROOT_PATH }); - await execAsync(`git commit -m "Updated CHANGELOGS"`, { cwd: MONOREPO_ROOT_PATH }); - await execAsync(`git push`, { cwd: MONOREPO_ROOT_PATH }); - utils.log(`Pushed CHANGELOG updates to Github`); - } - - await lernaPublishAsync(packageToVersionChange); -})().catch(err => { - utils.log(err); - process.exit(1); -}); - -async function lernaPublishAsync(packageToVersionChange: { [name: string]: string }) { - // HACK: Lerna publish does not provide a way to specify multiple package versions as - // flags so instead we need to interact with their interactive prompt interface. - const child = spawn('lerna', ['publish'], { cwd: MONOREPO_ROOT_PATH }); - child.stdout.on('data', (data: Buffer) => { - const output = data.toString('utf8'); - const isVersionPrompt = _.includes(output, 'Select a new version'); - if (isVersionPrompt) { - const outputStripLeft = output.split('new version for ')[1]; - const packageName = outputStripLeft.split(' ')[0]; - let versionChange = packageToVersionChange[packageName]; - const isPrivatePackage = _.isUndefined(versionChange); - if (isPrivatePackage) { - versionChange = 'patch'; // Always patch updates to private packages. - } - child.stdin.write(`${semverNameToIndex[versionChange]}\n`); - } - const isFinalPrompt = _.includes(output, 'Are you sure you want to publish the above changes?'); - if (isFinalPrompt && !IS_DRY_RUN) { - child.stdin.write(`y\n`); - } - }); -} - -async function getPublicLernaUpdatedPackagesAsync(): Promise { - const result = await execAsync(`${LERNA_EXECUTABLE} updated --json`, { cwd: MONOREPO_ROOT_PATH }); - const updatedPackages = JSON.parse(result.stdout); - const updatedPublicPackages = _.filter(updatedPackages, updatedPackage => !updatedPackage.private); - return updatedPublicPackages; -} - -function updateVersionNumberIfNeeded(currentVersion: string, proposedNextVersion: string) { - if (proposedNextVersion === currentVersion) { - return utils.getNextPatchVersion(currentVersion); - } - const sortedVersions = semverSort.desc([proposedNextVersion, currentVersion]); - if (sortedVersions[0] !== proposedNextVersion) { - return utils.getNextPatchVersion(currentVersion); - } - return proposedNextVersion; -} - -function getChangelogJSONOrCreateIfMissing(packageName: string, changelogPath: string): string { - let changelogJSON: string; - try { - changelogJSON = fs.readFileSync(changelogPath, 'utf-8'); - return changelogJSON; - } catch (err) { - // If none exists, create new, empty one. - const emptyChangelogJSON = JSON.stringify([]); - fs.writeFileSync(changelogPath, emptyChangelogJSON); - return emptyChangelogJSON; - } -} - -function shouldAddNewChangelogEntry(changelogs: Changelog[]): boolean { - if (_.isEmpty(changelogs)) { - return true; - } - const lastEntry = changelogs[0]; - return !!lastEntry.isPublished; -} - -function generateChangelogMd(changelogs: Changelog[]): string { - let changelogMd = ` - -CHANGELOG - `; - - _.each(changelogs, changelog => { - if (_.isUndefined(changelog.timestamp)) { - throw new Error( - 'All CHANGELOG.json entries must be updated to include a timestamp before generating their MD version', - ); - } - const date = moment(`${changelog.timestamp}`, 'X').format('MMMM D, YYYY'); - const title = `\n## v${changelog.version} - _${date}_\n\n`; - changelogMd += title; - - let changes = ''; - _.each(changelog.changes, change => { - let line = ` * ${change.note}`; - if (!_.isUndefined(change.pr)) { - line += ` (#${change.pr})`; - } - line += '\n'; - changes += line; - }); - changelogMd += `${changes}`; - }); - - return changelogMd; -} diff --git a/packages/monorepo-scripts/src/publish.ts b/packages/monorepo-scripts/src/publish.ts new file mode 100644 index 000000000..4265ee9e2 --- /dev/null +++ b/packages/monorepo-scripts/src/publish.ts @@ -0,0 +1,197 @@ +#!/usr/bin/env node + +import * as fs from 'fs'; +import lernaGetPackages = require('lerna-get-packages'); +import * as _ from 'lodash'; +import * as moment from 'moment'; +import * as path from 'path'; +import { exec as execAsync, spawn } from 'promisify-child-process'; +import semverDiff = require('semver-diff'); +import semverSort = require('semver-sort'); + +import { Changelog, Changes, SemVerIndex, UpdatedPackage } from './types'; +import { utils } from './utils'; + +const IS_DRY_RUN = true; +const MONOREPO_ROOT_PATH = path.join(__dirname, '../../..'); +const TODAYS_TIMESTAMP = moment().unix(); +const LERNA_EXECUTABLE = './node_modules/lerna/bin/lerna.js'; +const semverNameToIndex: { [semver: string]: number } = { + patch: SemVerIndex.Patch, + minor: SemVerIndex.Minor, + major: SemVerIndex.Major, +}; + +(async () => { + const updatedPublicPackages = await getPublicLernaUpdatedPackagesAsync(); + const updatedPackageNames = _.map(updatedPublicPackages, pkg => pkg.name); + + const allLernaPackages = lernaGetPackages(MONOREPO_ROOT_PATH); + const relevantLernaPackages = _.filter(allLernaPackages, pkg => { + return _.includes(updatedPackageNames, pkg.package.name); + }); + const relevantPackageNames = _.map(relevantLernaPackages, pkg => pkg.package.name); + utils.log(`Will update CHANGELOGs and publish: \n${relevantPackageNames.join('\n')}\n`); + + const packageToVersionChange: { [name: string]: string } = {}; + _.each(relevantLernaPackages, lernaPackage => { + const packageName = lernaPackage.package.name; + const changelogJSONPath = path.join(lernaPackage.location, 'CHANGELOG.json'); + const changelogJSON = getChangelogJSONOrCreateIfMissing(lernaPackage.package.name, changelogJSONPath); + let changelogs: Changelog[]; + try { + changelogs = JSON.parse(changelogJSON); + } catch (err) { + throw new Error( + `${lernaPackage.package.name}'s CHANGELOG.json contains invalid JSON. Please fix and try again.`, + ); + } + + const currentVersion = lernaPackage.package.version; + const shouldAddNewEntry = shouldAddNewChangelogEntry(changelogs); + if (shouldAddNewEntry) { + // Create a new entry for a patch version with generic changelog entry. + const nextPatchVersion = utils.getNextPatchVersion(currentVersion); + const newChangelogEntry: Changelog = { + timestamp: TODAYS_TIMESTAMP, + version: nextPatchVersion, + changes: [ + { + note: 'Dependencies updated', + }, + ], + }; + changelogs = [newChangelogEntry, ...changelogs]; + packageToVersionChange[packageName] = semverDiff(currentVersion, nextPatchVersion); + } else { + // Update existing entry with timestamp + const lastEntry = changelogs[0]; + if (_.isUndefined(lastEntry.timestamp)) { + lastEntry.timestamp = TODAYS_TIMESTAMP; + } + // Check version number is correct. + const proposedNextVersion = lastEntry.version; + lastEntry.version = updateVersionNumberIfNeeded(currentVersion, proposedNextVersion); + changelogs[0] = lastEntry; + packageToVersionChange[packageName] = semverDiff(currentVersion, lastEntry.version); + } + + // Save updated CHANGELOG.json + fs.writeFileSync(changelogJSONPath, JSON.stringify(changelogs, null, '\t')); + utils.log(`${packageName}: Updated CHANGELOG.json`); + // Generate updated CHANGELOG.md + const changelogMd = generateChangelogMd(changelogs); + const changelogMdPath = path.join(lernaPackage.location, 'CHANGELOG.md'); + fs.writeFileSync(changelogMdPath, changelogMd); + utils.log(`${packageName}: Updated CHANGELOG.md`); + }); + + if (!IS_DRY_RUN) { + await execAsync(`git add . --all`, { cwd: MONOREPO_ROOT_PATH }); + await execAsync(`git commit -m "Updated CHANGELOGS"`, { cwd: MONOREPO_ROOT_PATH }); + await execAsync(`git push`, { cwd: MONOREPO_ROOT_PATH }); + utils.log(`Pushed CHANGELOG updates to Github`); + } + + await lernaPublishAsync(packageToVersionChange); +})().catch(err => { + utils.log(err); + process.exit(1); +}); + +async function lernaPublishAsync(packageToVersionChange: { [name: string]: string }) { + // HACK: Lerna publish does not provide a way to specify multiple package versions as + // flags so instead we need to interact with their interactive prompt interface. + const child = spawn('lerna', ['publish'], { cwd: MONOREPO_ROOT_PATH }); + child.stdout.on('data', (data: Buffer) => { + const output = data.toString('utf8'); + const isVersionPrompt = _.includes(output, 'Select a new version'); + if (isVersionPrompt) { + const outputStripLeft = output.split('new version for ')[1]; + const packageName = outputStripLeft.split(' ')[0]; + let versionChange = packageToVersionChange[packageName]; + const isPrivatePackage = _.isUndefined(versionChange); + if (isPrivatePackage) { + versionChange = 'patch'; // Always patch updates to private packages. + } + child.stdin.write(`${semverNameToIndex[versionChange]}\n`); + } + const isFinalPrompt = _.includes(output, 'Are you sure you want to publish the above changes?'); + if (isFinalPrompt && !IS_DRY_RUN) { + child.stdin.write(`y\n`); + } + }); +} + +async function getPublicLernaUpdatedPackagesAsync(): Promise { + const result = await execAsync(`${LERNA_EXECUTABLE} updated --json`, { cwd: MONOREPO_ROOT_PATH }); + const updatedPackages = JSON.parse(result.stdout); + const updatedPublicPackages = _.filter(updatedPackages, updatedPackage => !updatedPackage.private); + return updatedPublicPackages; +} + +function updateVersionNumberIfNeeded(currentVersion: string, proposedNextVersion: string) { + if (proposedNextVersion === currentVersion) { + return utils.getNextPatchVersion(currentVersion); + } + const sortedVersions = semverSort.desc([proposedNextVersion, currentVersion]); + if (sortedVersions[0] !== proposedNextVersion) { + return utils.getNextPatchVersion(currentVersion); + } + return proposedNextVersion; +} + +function getChangelogJSONOrCreateIfMissing(packageName: string, changelogPath: string): string { + let changelogJSON: string; + try { + changelogJSON = fs.readFileSync(changelogPath, 'utf-8'); + return changelogJSON; + } catch (err) { + // If none exists, create new, empty one. + const emptyChangelogJSON = JSON.stringify([]); + fs.writeFileSync(changelogPath, emptyChangelogJSON); + return emptyChangelogJSON; + } +} + +function shouldAddNewChangelogEntry(changelogs: Changelog[]): boolean { + if (_.isEmpty(changelogs)) { + return true; + } + const lastEntry = changelogs[0]; + return !!lastEntry.isPublished; +} + +function generateChangelogMd(changelogs: Changelog[]): string { + let changelogMd = ` + +CHANGELOG + `; + + _.each(changelogs, changelog => { + if (_.isUndefined(changelog.timestamp)) { + throw new Error( + 'All CHANGELOG.json entries must be updated to include a timestamp before generating their MD version', + ); + } + const date = moment(`${changelog.timestamp}`, 'X').format('MMMM D, YYYY'); + const title = `\n## v${changelog.version} - _${date}_\n\n`; + changelogMd += title; + + let changes = ''; + _.each(changelog.changes, change => { + let line = ` * ${change.note}`; + if (!_.isUndefined(change.pr)) { + line += ` (#${change.pr})`; + } + line += '\n'; + changes += line; + }); + changelogMd += `${changes}`; + }); + + return changelogMd; +} -- cgit v1.2.3