224 lines
9.2 KiB
JavaScript
224 lines
9.2 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import * as promisify from 'es6-promisify';
|
|
import * as _ from 'lodash';
|
|
import * as moment from 'moment';
|
|
import opn = require('opn');
|
|
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 { Package, PackageToVersionChange, SemVerIndex, VersionChangelog } from './types';
|
|
import { changelogUtils } from './utils/changelog_utils';
|
|
import { utils } from './utils/utils';
|
|
|
|
const DOC_GEN_COMMAND = 'docs:json';
|
|
const NPM_NAMESPACE = '@0xproject/';
|
|
const IS_DRY_RUN = process.env.IS_DRY_RUN === 'true';
|
|
const TODAYS_TIMESTAMP = moment().unix();
|
|
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 () => {
|
|
// Fetch public, updated Lerna packages
|
|
const shouldIncludePrivate = false;
|
|
const updatedPublicPackages = await utils.getUpdatedPackagesAsync(shouldIncludePrivate);
|
|
|
|
// await confirmDocPagesRenderAsync(updatedPublicPackages);
|
|
|
|
// Update CHANGELOGs
|
|
const updatedPublicPackageNames = _.map(updatedPublicPackages, pkg => pkg.packageJson.name);
|
|
utils.log(`Will update CHANGELOGs and publish: \n${updatedPublicPackageNames.join('\n')}\n`);
|
|
const packageToVersionChange = await updateChangeLogsAsync(updatedPublicPackages);
|
|
|
|
// Push changelog changes to Github
|
|
if (!IS_DRY_RUN) {
|
|
// await pushChangelogsToGithubAsync();
|
|
}
|
|
|
|
// Call LernaPublish
|
|
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 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 });
|
|
|
|
// 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]);
|
|
});
|
|
_.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(
|
|
`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);
|
|
});
|
|
|
|
prompt.start();
|
|
const message = 'Do all the doc pages render properly? (yn)';
|
|
const result = await promisify(prompt.get)([message]);
|
|
const didConfirm = result[message] === 'y';
|
|
if (!didConfirm) {
|
|
utils.log('Publish process aborted.');
|
|
process.exit(0);
|
|
}
|
|
}
|
|
|
|
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`);
|
|
}
|
|
|
|
async function updateChangeLogsAsync(updatedPublicPackages: Package[]): Promise<PackageToVersionChange> {
|
|
const packageToVersionChange: PackageToVersionChange = {};
|
|
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 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: nextPatchVersionIfValid,
|
|
changes: [
|
|
{
|
|
note: 'Dependencies updated',
|
|
},
|
|
],
|
|
};
|
|
changelog = [newChangelogEntry, ...changelog];
|
|
packageToVersionChange[packageName] = semverDiff(currentVersion, nextPatchVersionIfValid);
|
|
} else {
|
|
// Update existing entry with timestamp
|
|
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);
|
|
changelog[0] = lastEntry;
|
|
packageToVersionChange[packageName] = semverDiff(currentVersion, lastEntry.version);
|
|
}
|
|
|
|
// Save updated CHANGELOG.json
|
|
await changelogUtils.writeChangelogJsonFileAsync(pkg.location, changelog);
|
|
utils.log(`${packageName}: Updated CHANGELOG.json`);
|
|
// Generate updated CHANGELOG.md
|
|
const changelogMd = changelogUtils.generateChangelogMd(changelog);
|
|
await changelogUtils.writeChangelogMdFileAsync(pkg.location, changelogMd);
|
|
utils.log(`${packageName}: Updated CHANGELOG.md`);
|
|
}
|
|
|
|
return packageToVersionChange;
|
|
}
|
|
|
|
async function lernaPublishAsync(packageToVersionChange: { [name: string]: string }): Promise<void> {
|
|
// 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', `--skip-git`], {
|
|
cwd: constants.monorepoRootPath,
|
|
});
|
|
let shouldPrintOutput = false;
|
|
let packageName: string;
|
|
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];
|
|
packageName = outputStripLeft.split(' ')[0];
|
|
child.stdin.write(`${SemVerIndex.Custom}\n`);
|
|
return;
|
|
}
|
|
const isCustomVersionPrompt = _.includes(output, 'Enter a custom version');
|
|
if (isCustomVersionPrompt) {
|
|
const versionChange = packageToVersionChange[packageName];
|
|
child.stdin.write(`${versionChange}\n`);
|
|
return;
|
|
}
|
|
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.`,
|
|
);
|
|
}
|
|
});
|
|
child.stderr.on('data', (data: Buffer) => {
|
|
const output = data.toString('utf8');
|
|
utils.log('Stderr:', output);
|
|
});
|
|
}
|
|
|
|
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 updatedVersionIfValid;
|
|
}
|
|
const sortedVersions = semverSort.desc([proposedNextVersion, currentVersion]);
|
|
if (sortedVersions[0] !== proposedNextVersion) {
|
|
return updatedVersionIfValid;
|
|
}
|
|
return proposedNextVersion;
|
|
}
|