diff options
author | Alex Browne <stephenalexbrowne@gmail.com> | 2018-08-14 09:42:09 +0800 |
---|---|---|
committer | Alex Browne <stephenalexbrowne@gmail.com> | 2018-08-14 09:42:09 +0800 |
commit | 88766a02c7e6688e72d5c4c69ce68028b322f154 (patch) | |
tree | fa06552a80249e7998691b64df6b3b2827f9f947 /packages/monorepo-scripts/src/publish.ts | |
parent | 8162394797342cef268cc8072fc860326974e269 (diff) | |
parent | fadd292ecf367e42154856509d0ea0c20b23f2f1 (diff) | |
download | dexon-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.ts | 323 |
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; -} |