aboutsummaryrefslogtreecommitdiffstats
path: root/packages/monorepo-scripts/src/publish.ts
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2018-04-02 20:23:07 +0800
committerFabio Berger <me@fabioberger.com>2018-04-02 20:23:07 +0800
commit8162394797342cef268cc8072fc860326974e269 (patch)
tree2826b02715a8cb794571be6c7dccdb395329361c /packages/monorepo-scripts/src/publish.ts
parentfd001186dd281a11920246c6b9afcefe1d55bc23 (diff)
parent695b697cdf6c73bb4b5f920869ce128f9a9e7523 (diff)
downloaddexon-0x-contracts-8162394797342cef268cc8072fc860326974e269.tar
dexon-0x-contracts-8162394797342cef268cc8072fc860326974e269.tar.gz
dexon-0x-contracts-8162394797342cef268cc8072fc860326974e269.tar.bz2
dexon-0x-contracts-8162394797342cef268cc8072fc860326974e269.tar.lz
dexon-0x-contracts-8162394797342cef268cc8072fc860326974e269.tar.xz
dexon-0x-contracts-8162394797342cef268cc8072fc860326974e269.tar.zst
dexon-0x-contracts-8162394797342cef268cc8072fc860326974e269.zip
Merge branch 'development'
* development: (175 commits) small README fixes Update docs list in README Add manual postpublish command to all public packages and update CHANGELOG.json Fix postpublish util to ignore namespace Fix release notes bug Should print out `lerna publish` stdout so we can see if anything went wrong Publish Updated CHANGELOGS Generate CHANGELOG.json files Fix hasty find/replace Default to 4sp Update moment, no longer need separate moment types Move prettify command to utils and also call it on CHANGELOG.md Add prettier run on generated CHANGELOG.json and fix scripts Remove semi-colons from monorepo-scripts package.json Get rid of ; in top-level package.json Fix TSLint error Make dry-run configurable from top-level package.json Improve naming Run prettier, update deployer CHANGELOG ...
Diffstat (limited to 'packages/monorepo-scripts/src/publish.ts')
-rw-r--r--packages/monorepo-scripts/src/publish.ts217
1 files changed, 217 insertions, 0 deletions
diff --git a/packages/monorepo-scripts/src/publish.ts b/packages/monorepo-scripts/src/publish.ts
new file mode 100644
index 000000000..d749ec630
--- /dev/null
+++ b/packages/monorepo-scripts/src/publish.ts
@@ -0,0 +1,217 @@
+#!/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 { constants } from './constants';
+import { Changelog, Changes, SemVerIndex, UpdatedPackage } from './types';
+import { utils } from './utils';
+
+const IS_DRY_RUN = process.env.IS_DRY_RUN === 'true';
+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(constants.monorepoRootPath);
+ const updatedPublicLernaPackages = _.filter(allLernaPackages, pkg => {
+ return _.includes(updatedPackageNames, pkg.package.name);
+ });
+ const updatedPublicLernaPackageNames = _.map(updatedPublicLernaPackages, pkg => pkg.package.name);
+ utils.log(`Will update CHANGELOGs and publish: \n${updatedPublicLernaPackageNames.join('\n')}\n`);
+
+ const packageToVersionChange: { [name: string]: string } = {};
+ for (const lernaPackage of updatedPublicLernaPackages) {
+ 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, 4));
+ await utils.prettifyAsync(changelogJSONPath, constants.monorepoRootPath);
+ 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);
+ await utils.prettifyAsync(changelogMdPath, constants.monorepoRootPath);
+ utils.log(`${packageName}: Updated CHANGELOG.md`);
+ }
+
+ if (!IS_DRY_RUN) {
+ await execAsync(`git add . --all`, { cwd: constants.monorepoRootPath });
+ await execAsync(`git commit -m "Updated CHANGELOGS"`, { cwd: constants.monorepoRootPath });
+ await execAsync(`git push`, { cwd: constants.monorepoRootPath });
+ utils.log(`Pushed CHANGELOG updates to Github`);
+ }
+
+ utils.log('Version updates to apply:');
+ _.each(packageToVersionChange, (versionChange: string, packageName: string) => {
+ utils.log(`${packageName} -> ${versionChange}`);
+ });
+ utils.log(`Calling 'lerna publish'...`);
+ 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 via
+ // flags so instead we need to interact with their interactive prompt interface.
+ const child = spawn('lerna', ['publish', '--registry=https://registry.npmjs.org/'], {
+ cwd: constants.monorepoRootPath,
+ });
+ let shouldPrintOutput = false;
+ child.stdout.on('data', (data: Buffer) => {
+ const output = data.toString('utf8');
+ if (shouldPrintOutput) {
+ utils.log(output);
+ }
+ 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.
+ }
+ const semVerIndex = semverNameToIndex[versionChange];
+ child.stdin.write(`${semVerIndex}\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`);
+ // After confirmations, we want to print the output to watch the `lerna publish` command
+ shouldPrintOutput = true;
+ } else if (isFinalPrompt && IS_DRY_RUN) {
+ utils.log(
+ `Submitted all versions to Lerna but since this is a dry run, did not confirm. You need to CTRL-C to exit.`,
+ );
+ }
+ });
+}
+
+async function getPublicLernaUpdatedPackagesAsync(): Promise<UpdatedPackage[]> {
+ const result = await execAsync(`${LERNA_EXECUTABLE} updated --json`, { cwd: constants.monorepoRootPath });
+ 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([], null, 4);
+ 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 = `<!--
+This file is auto-generated using the monorepo-scripts package. Don't edit directly.
+Edit the package's CHANGELOG.json file only.
+-->
+
+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;
+}