aboutsummaryrefslogtreecommitdiffstats
path: root/packages/monorepo-scripts/src/publish.ts
diff options
context:
space:
mode:
authorAlex Browne <stephenalexbrowne@gmail.com>2018-08-14 09:42:09 +0800
committerAlex Browne <stephenalexbrowne@gmail.com>2018-08-14 09:42:09 +0800
commit88766a02c7e6688e72d5c4c69ce68028b322f154 (patch)
treefa06552a80249e7998691b64df6b3b2827f9f947 /packages/monorepo-scripts/src/publish.ts
parent8162394797342cef268cc8072fc860326974e269 (diff)
parentfadd292ecf367e42154856509d0ea0c20b23f2f1 (diff)
downloaddexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar
dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.gz
dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.bz2
dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.lz
dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.xz
dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.zst
dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.zip
Merge branch 'development'
Diffstat (limited to 'packages/monorepo-scripts/src/publish.ts')
-rw-r--r--packages/monorepo-scripts/src/publish.ts323
1 files changed, 160 insertions, 163 deletions
diff --git a/packages/monorepo-scripts/src/publish.ts b/packages/monorepo-scripts/src/publish.ts
index d749ec630..6ff0c9bef 100644
--- a/packages/monorepo-scripts/src/publish.ts
+++ b/packages/monorepo-scripts/src/publish.ts
@@ -1,217 +1,214 @@
#!/usr/bin/env node
-import * as fs from 'fs';
-import lernaGetPackages = require('lerna-get-packages');
+import * as promisify from 'es6-promisify';
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 opn = require('opn');
+import { exec as execAsync } from 'promisify-child-process';
+import * as prompt from 'prompt';
+import semver = require('semver');
import semverSort = require('semver-sort');
import { constants } from './constants';
-import { Changelog, Changes, SemVerIndex, UpdatedPackage } from './types';
-import { utils } from './utils';
+import { Package, PackageToNextVersion, VersionChangelog } from './types';
+import { changelogUtils } from './utils/changelog_utils';
+import { configs } from './utils/configs';
+import { utils } from './utils/utils';
-const IS_DRY_RUN = process.env.IS_DRY_RUN === 'true';
+const DOC_GEN_COMMAND = 'docs:json';
+const NPM_NAMESPACE = '@0xproject/';
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,
+const packageNameToWebsitePath: { [name: string]: string } = {
+ '0x.js': '0xjs',
+ 'web3-wrapper': 'web3_wrapper',
+ contracts: 'contracts',
+ connect: 'connect',
+ 'json-schemas': 'json-schemas',
+ 'sol-compiler': 'sol-compiler',
+ 'sol-cov': 'sol-cov',
+ subproviders: 'subproviders',
+ 'order-utils': 'order-utils',
+ 'ethereum-types': 'ethereum-types',
};
+async function confirmAsync(message: string): Promise<void> {
+ prompt.start();
+ const result = await promisify(prompt.get)([message]);
+ const didConfirm = result[message] === 'y';
+ if (!didConfirm) {
+ utils.log('Publish process aborted.');
+ process.exit(0);
+ }
+}
+
(async () => {
- const updatedPublicPackages = await getPublicLernaUpdatedPackagesAsync();
- const updatedPackageNames = _.map(updatedPublicPackages, pkg => pkg.name);
+ // Fetch public, updated Lerna packages
+ const shouldIncludePrivate = true;
+ const allUpdatedPackages = await utils.getUpdatedPackagesAsync(shouldIncludePrivate);
+
+ if (!configs.IS_LOCAL_PUBLISH) {
+ await confirmAsync(
+ 'THIS IS NOT A TEST PUBLISH! You are about to publish one or more packages to npm. Are you sure you want to continue? (y/n)',
+ );
+ await confirmDocPagesRenderAsync(allUpdatedPackages);
+ }
+
+ // Update CHANGELOGs
+ const updatedPublicPackages = _.filter(allUpdatedPackages, pkg => !pkg.packageJson.private);
+ const updatedPublicPackageNames = _.map(updatedPublicPackages, pkg => pkg.packageJson.name);
+ utils.log(`Will update CHANGELOGs and publish: \n${updatedPublicPackageNames.join('\n')}\n`);
+ const packageToNextVersion = await updateChangeLogsAsync(updatedPublicPackages);
+
+ const updatedPrivatePackages = _.filter(allUpdatedPackages, pkg => pkg.packageJson.private);
+ _.each(updatedPrivatePackages, pkg => {
+ const currentVersion = pkg.packageJson.version;
+ const packageName = pkg.packageJson.name;
+ const nextPatchVersionIfValid = semver.inc(currentVersion, 'patch');
+ if (!_.isNull(nextPatchVersionIfValid)) {
+ packageToNextVersion[packageName] = nextPatchVersionIfValid;
+ } else {
+ throw new Error(`Encountered invalid semver version: ${currentVersion} for package: ${packageName}`);
+ }
+ });
+
+ // Push changelog changes to Github
+ if (!configs.IS_LOCAL_PUBLISH) {
+ await pushChangelogsToGithubAsync();
+ }
+
+ // Call LernaPublish
+ utils.log('Version updates to apply:');
+ _.each(packageToNextVersion, (versionChange: string, packageName: string) => {
+ utils.log(`${packageName} -> ${versionChange}`);
+ });
+ utils.log(`Calling 'lerna publish'...`);
+ await lernaPublishAsync(packageToNextVersion);
+})().catch(err => {
+ utils.log(err);
+ process.exit(1);
+});
+
+async function confirmDocPagesRenderAsync(packages: Package[]): Promise<void> {
+ // push docs to staging
+ utils.log("Upload all docJson's to S3 staging...");
+ await execAsync(`yarn stage_docs`, { cwd: constants.monorepoRootPath });
- const allLernaPackages = lernaGetPackages(constants.monorepoRootPath);
- const updatedPublicLernaPackages = _.filter(allLernaPackages, pkg => {
- return _.includes(updatedPackageNames, pkg.package.name);
+ // deploy website to staging
+ utils.log('Deploy website to staging...');
+ const pathToWebsite = `${constants.monorepoRootPath}/packages/website`;
+ await execAsync(`yarn deploy_staging`, { cwd: pathToWebsite });
+
+ const packagesWithDocs = _.filter(packages, pkg => {
+ const scriptsIfExists = pkg.packageJson.scripts;
+ if (_.isUndefined(scriptsIfExists)) {
+ throw new Error('Found a public package without any scripts in package.json');
+ }
+ return !_.isUndefined(scriptsIfExists[DOC_GEN_COMMAND]);
});
- 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) {
+ _.each(packagesWithDocs, pkg => {
+ const name = pkg.packageJson.name;
+ const nameWithoutPrefix = _.startsWith(name, NPM_NAMESPACE) ? name.split('@0xproject/')[1] : name;
+ const docSegmentIfExists = packageNameToWebsitePath[nameWithoutPrefix];
+ if (_.isUndefined(docSegmentIfExists)) {
throw new Error(
- `${lernaPackage.package.name}'s CHANGELOG.json contains invalid JSON. Please fix and try again.`,
+ `Found package '${name}' with doc commands but no corresponding docSegment in monorepo_scripts
+package.ts. Please add an entry for it and try again.`,
);
}
+ const link = `${constants.stagingWebsite}/docs/${docSegmentIfExists}`;
+ // tslint:disable-next-line:no-floating-promises
+ opn(link);
+ });
+
+ await confirmAsync('Do all the doc pages render properly? (y/n)');
+}
+
+async function pushChangelogsToGithubAsync(): Promise<void> {
+ 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`);
+}
- const currentVersion = lernaPackage.package.version;
- const shouldAddNewEntry = shouldAddNewChangelogEntry(changelogs);
+async function updateChangeLogsAsync(updatedPublicPackages: Package[]): Promise<PackageToNextVersion> {
+ const packageToNextVersion: PackageToNextVersion = {};
+ for (const pkg of updatedPublicPackages) {
+ const packageName = pkg.packageJson.name;
+ let changelog = changelogUtils.getChangelogOrCreateIfMissing(packageName, pkg.location);
+
+ const currentVersion = pkg.packageJson.version;
+ const shouldAddNewEntry = changelogUtils.shouldAddNewChangelogEntry(
+ pkg.packageJson.name,
+ currentVersion,
+ changelog,
+ );
if (shouldAddNewEntry) {
// Create a new entry for a patch version with generic changelog entry.
- const nextPatchVersion = utils.getNextPatchVersion(currentVersion);
- const newChangelogEntry: Changelog = {
+ 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',
},
],
};
- changelogs = [newChangelogEntry, ...changelogs];
- packageToVersionChange[packageName] = semverDiff(currentVersion, nextPatchVersion);
+ changelog = [newChangelogEntry, ...changelog];
+ packageToNextVersion[packageName] = nextPatchVersionIfValid;
} else {
// Update existing entry with timestamp
- const lastEntry = changelogs[0];
+ const lastEntry = changelog[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);
+ changelog[0] = lastEntry;
+ packageToNextVersion[packageName] = lastEntry.version;
}
// Save updated CHANGELOG.json
- fs.writeFileSync(changelogJSONPath, JSON.stringify(changelogs, null, 4));
- await utils.prettifyAsync(changelogJSONPath, constants.monorepoRootPath);
+ await changelogUtils.writeChangelogJsonFileAsync(pkg.location, changelog);
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);
+ const changelogMd = changelogUtils.generateChangelogMd(changelog);
+ await changelogUtils.writeChangelogMdFileAsync(pkg.location, changelogMd);
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.`,
- );
- }
- });
+ return packageToNextVersion;
}
-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;
+async function lernaPublishAsync(packageToNextVersion: { [name: string]: string }): Promise<void> {
+ const packageVersionString = _.map(packageToNextVersion, (nextVersion: string, packageName: string) => {
+ return `${packageName}@${nextVersion}`;
+ }).join(',');
+ let lernaPublishCmd = `node ${constants.lernaExecutable} publish --cdVersions=${packageVersionString} --registry=${
+ configs.NPM_REGISTRY_URL
+ } --yes`;
+ if (configs.IS_LOCAL_PUBLISH) {
+ lernaPublishCmd += ` --skip-git`;
+ }
+ utils.log('Lerna is publishing...');
+ await execAsync(lernaPublishCmd, { cwd: constants.monorepoRootPath });
}
-function updateVersionNumberIfNeeded(currentVersion: string, proposedNextVersion: string) {
+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;
}
-
-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;
-}