diff options
-rw-r--r-- | package.json | 6 | ||||
-rw-r--r-- | packages/contracts/src/utils/erc20_wrapper.ts | 48 | ||||
-rw-r--r-- | packages/contracts/src/utils/erc721_wrapper.ts | 56 | ||||
-rw-r--r-- | packages/contracts/src/utils/order_factory.ts | 3 | ||||
-rw-r--r-- | packages/monorepo-scripts/CHANGELOG.json | 9 | ||||
-rw-r--r-- | packages/monorepo-scripts/package.json | 1 | ||||
-rw-r--r-- | packages/monorepo-scripts/src/deps_versions.ts | 2 | ||||
-rw-r--r-- | packages/monorepo-scripts/src/find_unused_dependencies.ts | 2 | ||||
-rw-r--r-- | packages/monorepo-scripts/src/postpublish_utils.ts | 2 | ||||
-rw-r--r-- | packages/monorepo-scripts/src/prepublish_checks.ts | 71 | ||||
-rw-r--r-- | packages/monorepo-scripts/src/publish.ts | 123 | ||||
-rw-r--r-- | packages/monorepo-scripts/src/remove_tags.ts | 4 | ||||
-rw-r--r-- | packages/monorepo-scripts/src/test_installation.ts | 2 | ||||
-rw-r--r-- | packages/monorepo-scripts/src/types.ts | 8 | ||||
-rw-r--r-- | packages/monorepo-scripts/src/utils/changelog_utils.ts | 55 | ||||
-rw-r--r-- | packages/monorepo-scripts/src/utils/utils.ts (renamed from packages/monorepo-scripts/src/utils.ts) | 4 |
16 files changed, 219 insertions, 177 deletions
diff --git a/package.json b/package.json index 177087f88..2ddde3484 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,9 @@ "prettier:ci": "prettier --list-different '**/*.{ts,tsx,json,md}' --config .prettierrc", "report_coverage": "lcov-result-merger 'packages/*/coverage/lcov.info' | coveralls", "test:installation": "node ./packages/monorepo-scripts/lib/test_installation.js", - "run:publish": "run-s install:all rebuild script:publish", - "run:publish:dry": "run-s install:all rebuild script:publish:dry", + "run:publish": "run-s install:all build:monorepo_scripts script:prepublish_checks rebuild script:publish", + "run:publish:dry": "run-s install:all build:monorepo_scripts script:prepublish_checks rebuild script:publish:dry", + "script:prepublish_checks": "node ./packages/monorepo-scripts/lib/prepublish_checks.js", "script:publish": "node ./packages/monorepo-scripts/lib/publish.js", "script:publish:dry": "IS_DRY_RUN=true yarn script:publish", "install:all": "yarn install", @@ -22,6 +23,7 @@ "lerna:run": "lerna run", "watch": "wsrun watch $PKG --fast-exit -r --stages --done-criteria='complete|successfully'", "build": "wsrun build $PKG --fast-exit -r --stages", + "build:monorepo_scripts": "PKG=@0xproject/monorepo-scripts yarn build", "clean": "wsrun clean $PKG --fast-exit -r --parallel", "rebuild": "run-s clean build", "test": "wsrun test $PKG --fast-exit --serial --exclude-missing", diff --git a/packages/contracts/src/utils/erc20_wrapper.ts b/packages/contracts/src/utils/erc20_wrapper.ts index 0f45fb1e6..dceeceeea 100644 --- a/packages/contracts/src/utils/erc20_wrapper.ts +++ b/packages/contracts/src/utils/erc20_wrapper.ts @@ -16,18 +16,19 @@ export class ERC20Wrapper { private _contractOwnerAddress: string; private _web3Wrapper: Web3Wrapper; private _provider: Provider; - private _dummyTokenContracts?: DummyERC20TokenContract[]; + private _dummyTokenContracts: DummyERC20TokenContract[]; private _proxyContract?: ERC20ProxyContract; constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) { + this._dummyTokenContracts = []; this._web3Wrapper = new Web3Wrapper(provider); this._provider = provider; this._tokenOwnerAddresses = tokenOwnerAddresses; this._contractOwnerAddress = contractOwnerAddress; } public async deployDummyTokensAsync(): Promise<DummyERC20TokenContract[]> { - this._dummyTokenContracts = await Promise.all( - _.times(constants.NUM_DUMMY_ERC20_TO_DEPLOY, async () => - DummyERC20TokenContract.deployFrom0xArtifactAsync( + for (let i = 0; i < constants.NUM_DUMMY_ERC20_TO_DEPLOY; i++) { + this._dummyTokenContracts.push( + await DummyERC20TokenContract.deployFrom0xArtifactAsync( artifacts.DummyERC20Token, this._provider, txDefaults, @@ -36,8 +37,8 @@ export class ERC20Wrapper { constants.DUMMY_TOKEN_DECIMALS, constants.DUMMY_TOKEN_TOTAL_SUPPLY, ), - ), - ); + ); + } return this._dummyTokenContracts; } public async deployProxyAsync(): Promise<ERC20ProxyContract> { @@ -51,44 +52,41 @@ export class ERC20Wrapper { public async setBalancesAndAllowancesAsync(): Promise<void> { this._validateDummyTokenContractsExistOrThrow(); this._validateProxyContractExistsOrThrow(); - const setBalancePromises: Array<Promise<string>> = []; - const setAllowancePromises: Array<Promise<string>> = []; - _.forEach(this._dummyTokenContracts, dummyTokenContract => { - _.forEach(this._tokenOwnerAddresses, tokenOwnerAddress => { - setBalancePromises.push( - dummyTokenContract.setBalance.sendTransactionAsync( + for (const dummyTokenContract of this._dummyTokenContracts) { + for (const tokenOwnerAddress of this._tokenOwnerAddresses) { + await this._web3Wrapper.awaitTransactionSuccessAsync( + await dummyTokenContract.setBalance.sendTransactionAsync( tokenOwnerAddress, constants.INITIAL_ERC20_BALANCE, { from: this._contractOwnerAddress }, ), + constants.AWAIT_TRANSACTION_MINED_MS, ); - setAllowancePromises.push( - dummyTokenContract.approve.sendTransactionAsync( + await this._web3Wrapper.awaitTransactionSuccessAsync( + await dummyTokenContract.approve.sendTransactionAsync( (this._proxyContract as ERC20ProxyContract).address, constants.INITIAL_ERC20_ALLOWANCE, { from: tokenOwnerAddress }, ), + constants.AWAIT_TRANSACTION_MINED_MS, ); - }); - }); - const txHashes = await Promise.all([...setBalancePromises, ...setAllowancePromises]); - await Promise.all(_.map(txHashes, async txHash => this._web3Wrapper.awaitTransactionSuccessAsync(txHash))); + } + } } public async getBalancesAsync(): Promise<ERC20BalancesByOwner> { this._validateDummyTokenContractsExistOrThrow(); const balancesByOwner: ERC20BalancesByOwner = {}; - const balancePromises: Array<Promise<BigNumber>> = []; + const balances: BigNumber[] = []; const balanceInfo: Array<{ tokenOwnerAddress: string; tokenAddress: string }> = []; - _.forEach(this._dummyTokenContracts, dummyTokenContract => { - _.forEach(this._tokenOwnerAddresses, tokenOwnerAddress => { - balancePromises.push(dummyTokenContract.balanceOf.callAsync(tokenOwnerAddress)); + for (const dummyTokenContract of this._dummyTokenContracts) { + for (const tokenOwnerAddress of this._tokenOwnerAddresses) { + balances.push(await dummyTokenContract.balanceOf.callAsync(tokenOwnerAddress)); balanceInfo.push({ tokenOwnerAddress, tokenAddress: dummyTokenContract.address, }); - }); - }); - const balances = await Promise.all(balancePromises); + } + } _.forEach(balances, (balance, balanceIndex) => { const tokenAddress = balanceInfo[balanceIndex].tokenAddress; const tokenOwnerAddress = balanceInfo[balanceIndex].tokenOwnerAddress; diff --git a/packages/contracts/src/utils/erc721_wrapper.ts b/packages/contracts/src/utils/erc721_wrapper.ts index 11a012602..13fdf630e 100644 --- a/packages/contracts/src/utils/erc721_wrapper.ts +++ b/packages/contracts/src/utils/erc721_wrapper.ts @@ -17,27 +17,28 @@ export class ERC721Wrapper { private _contractOwnerAddress: string; private _web3Wrapper: Web3Wrapper; private _provider: Provider; - private _dummyTokenContracts?: DummyERC721TokenContract[]; + private _dummyTokenContracts: DummyERC721TokenContract[]; private _proxyContract?: ERC721ProxyContract; private _initialTokenIdsByOwner: ERC721TokenIdsByOwner = {}; constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) { this._web3Wrapper = new Web3Wrapper(provider); this._provider = provider; + this._dummyTokenContracts = []; this._tokenOwnerAddresses = tokenOwnerAddresses; this._contractOwnerAddress = contractOwnerAddress; } public async deployDummyTokensAsync(): Promise<DummyERC721TokenContract[]> { - this._dummyTokenContracts = await Promise.all( - _.times(constants.NUM_DUMMY_ERC721_TO_DEPLOY, async () => - DummyERC721TokenContract.deployFrom0xArtifactAsync( + for (let i = 0; i < constants.NUM_DUMMY_ERC721_TO_DEPLOY; i++) { + this._dummyTokenContracts.push( + await DummyERC721TokenContract.deployFrom0xArtifactAsync( artifacts.DummyERC721Token, this._provider, txDefaults, constants.DUMMY_TOKEN_NAME, constants.DUMMY_TOKEN_SYMBOL, ), - ), - ); + ); + } return this._dummyTokenContracts; } public async deployProxyAsync(): Promise<ERC721ProxyContract> { @@ -51,17 +52,16 @@ export class ERC721Wrapper { public async setBalancesAndAllowancesAsync(): Promise<void> { this._validateDummyTokenContractsExistOrThrow(); this._validateProxyContractExistsOrThrow(); - const setBalancePromises: Array<Promise<string>> = []; - const setAllowancePromises: Array<Promise<string>> = []; this._initialTokenIdsByOwner = {}; - _.forEach(this._dummyTokenContracts, dummyTokenContract => { - _.forEach(this._tokenOwnerAddresses, tokenOwnerAddress => { - _.forEach(_.range(constants.NUM_ERC721_TOKENS_TO_MINT), () => { + for (const dummyTokenContract of this._dummyTokenContracts) { + for (const tokenOwnerAddress of this._tokenOwnerAddresses) { + for (let i = 0; i < constants.NUM_ERC721_TOKENS_TO_MINT; i++) { const tokenId = generatePseudoRandomSalt(); - setBalancePromises.push( - dummyTokenContract.mint.sendTransactionAsync(tokenOwnerAddress, tokenId, { + await this._web3Wrapper.awaitTransactionSuccessAsync( + await dummyTokenContract.mint.sendTransactionAsync(tokenOwnerAddress, tokenId, { from: this._contractOwnerAddress, }), + constants.AWAIT_TRANSACTION_MINED_MS, ); if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress])) { this._initialTokenIdsByOwner[tokenOwnerAddress] = { @@ -72,41 +72,39 @@ export class ERC721Wrapper { this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address] = []; } this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address].push(tokenId); - }); + } const shouldApprove = true; - setAllowancePromises.push( - dummyTokenContract.setApprovalForAll.sendTransactionAsync( + await this._web3Wrapper.awaitTransactionSuccessAsync( + await dummyTokenContract.setApprovalForAll.sendTransactionAsync( (this._proxyContract as ERC721ProxyContract).address, shouldApprove, { from: tokenOwnerAddress }, ), + constants.AWAIT_TRANSACTION_MINED_MS, ); - }); - }); - const txHashes = await Promise.all([...setBalancePromises, ...setAllowancePromises]); - await Promise.all(_.map(txHashes, async txHash => this._web3Wrapper.awaitTransactionSuccessAsync(txHash))); + } + } } public async getBalancesAsync(): Promise<ERC721TokenIdsByOwner> { this._validateDummyTokenContractsExistOrThrow(); this._validateBalancesAndAllowancesSetOrThrow(); const tokenIdsByOwner: ERC721TokenIdsByOwner = {}; - const tokenOwnerPromises: Array<Promise<string>> = []; + const tokenOwnerAddresses: string[] = []; const tokenInfo: Array<{ tokenId: BigNumber; tokenAddress: string }> = []; - _.forEach(this._dummyTokenContracts, dummyTokenContract => { - _.forEach(this._tokenOwnerAddresses, tokenOwnerAddress => { + for (const dummyTokenContract of this._dummyTokenContracts) { + for (const tokenOwnerAddress of this._tokenOwnerAddresses) { const initialTokenOwnerIds = this._initialTokenIdsByOwner[tokenOwnerAddress][ dummyTokenContract.address ]; - _.forEach(initialTokenOwnerIds, tokenId => { - tokenOwnerPromises.push(dummyTokenContract.ownerOf.callAsync(tokenId)); + for (const tokenId of initialTokenOwnerIds) { + tokenOwnerAddresses.push(await dummyTokenContract.ownerOf.callAsync(tokenId)); tokenInfo.push({ tokenId, tokenAddress: dummyTokenContract.address, }); - }); - }); - }); - const tokenOwnerAddresses = await Promise.all(tokenOwnerPromises); + } + } + } _.forEach(tokenOwnerAddresses, (tokenOwnerAddress, ownerIndex) => { const tokenAddress = tokenInfo[ownerIndex].tokenAddress; const tokenId = tokenInfo[ownerIndex].tokenId; diff --git a/packages/contracts/src/utils/order_factory.ts b/packages/contracts/src/utils/order_factory.ts index ef11e4341..af411c01f 100644 --- a/packages/contracts/src/utils/order_factory.ts +++ b/packages/contracts/src/utils/order_factory.ts @@ -17,7 +17,8 @@ export class OrderFactory { customOrderParams: Partial<Order> = {}, signatureType: SignatureType = SignatureType.EthSign, ): SignedOrder { - const randomExpiration = new BigNumber(Math.floor((Date.now() + Math.random() * 100000000000) / 1000)); + const tenMinutes = 10 * 60 * 1000; + const randomExpiration = new BigNumber(Date.now() + tenMinutes); const order = ({ senderAddress: constants.NULL_ADDRESS, expirationTimeSeconds: randomExpiration, diff --git a/packages/monorepo-scripts/CHANGELOG.json b/packages/monorepo-scripts/CHANGELOG.json index 58d946cd6..a41262ed2 100644 --- a/packages/monorepo-scripts/CHANGELOG.json +++ b/packages/monorepo-scripts/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "0.2.0", + "changes": [ + { + "note": "Add `prepublish_checks` script", + "pr": 650 + } + ] + }, + { "timestamp": 1527008794, "version": "0.1.20", "changes": [ diff --git a/packages/monorepo-scripts/package.json b/packages/monorepo-scripts/package.json index 02a8e4a42..2bbf4df11 100644 --- a/packages/monorepo-scripts/package.json +++ b/packages/monorepo-scripts/package.json @@ -16,6 +16,7 @@ "find_unused_deps": "run-s build script:find_unused_deps", "remove_tags": "run-s build script:remove_tags", "script:deps_versions": "node ./lib/deps_versions.js", + "script:prepublish_checks": "node ./lib/prepublish_checks.js", "script:publish": "IS_DRY_RUN=true node ./lib/publish.js", "script:find_unused_deps": "node ./lib/find_unused_dependencies.js", "script:remove_tags": "node ./lib/remove_tags.js" diff --git a/packages/monorepo-scripts/src/deps_versions.ts b/packages/monorepo-scripts/src/deps_versions.ts index 07292a160..f090d12e9 100644 --- a/packages/monorepo-scripts/src/deps_versions.ts +++ b/packages/monorepo-scripts/src/deps_versions.ts @@ -5,7 +5,7 @@ import * as fs from 'fs'; import { sync as globSync } from 'glob'; import * as _ from 'lodash'; -import { utils } from './utils'; +import { utils } from './utils/utils'; interface Dependencies { [depName: string]: string; diff --git a/packages/monorepo-scripts/src/find_unused_dependencies.ts b/packages/monorepo-scripts/src/find_unused_dependencies.ts index df303f6ce..71e707224 100644 --- a/packages/monorepo-scripts/src/find_unused_dependencies.ts +++ b/packages/monorepo-scripts/src/find_unused_dependencies.ts @@ -7,7 +7,7 @@ import * as _ from 'lodash'; import { exec as execAsync } from 'promisify-child-process'; import { constants } from './constants'; -import { utils } from './utils'; +import { utils } from './utils/utils'; // For some reason, `depcheck` hangs on some packages. Add them here. const IGNORE_PACKAGES = ['@0xproject/sol-compiler']; diff --git a/packages/monorepo-scripts/src/postpublish_utils.ts b/packages/monorepo-scripts/src/postpublish_utils.ts index f5785343d..dbbde894d 100644 --- a/packages/monorepo-scripts/src/postpublish_utils.ts +++ b/packages/monorepo-scripts/src/postpublish_utils.ts @@ -7,7 +7,7 @@ import * as publishRelease from 'publish-release'; import semverSort = require('semver-sort'); import { constants } from './constants'; -import { utils } from './utils'; +import { utils } from './utils/utils'; const publishReleaseAsync = promisify(publishRelease); const generatedDocsDirectoryName = 'generated_docs'; diff --git a/packages/monorepo-scripts/src/prepublish_checks.ts b/packages/monorepo-scripts/src/prepublish_checks.ts new file mode 100644 index 000000000..2c096d8f6 --- /dev/null +++ b/packages/monorepo-scripts/src/prepublish_checks.ts @@ -0,0 +1,71 @@ +import * as _ from 'lodash'; +import { exec as execAsync } from 'promisify-child-process'; + +import { constants } from './constants'; +import { utils } from './utils/utils'; + +async function checkPublishRequiredSetupAsync(): Promise<void> { + // check to see if logged into npm before publishing + try { + // HACK: for some reason on some setups, the `npm whoami` will not recognize a logged-in user + // unless run with `sudo` (i.e Fabio's NVM setup) but is fine for others (Jacob's NVM setup). + utils.log('Checking that the user is logged in on npm...'); + await execAsync(`sudo npm whoami`); + } catch (err) { + throw new Error('You must be logged into npm in the commandline to publish. Run `npm login` and try again.'); + } + + // Check to see if Git personal token setup + if (_.isUndefined(constants.githubPersonalAccessToken)) { + throw new Error( + 'You must have a Github personal access token set to an envVar named `GITHUB_PERSONAL_ACCESS_TOKEN_0X_JS`. Add it then try again.', + ); + } + + // Check Yarn version is 1.X + utils.log('Checking the yarn version...'); + const result = await execAsync(`yarn --version`); + const version = result.stdout; + const versionSegments = version.split('.'); + const majorVersion = _.parseInt(versionSegments[0]); + if (majorVersion < 1) { + throw new Error('Your yarn version must be v1.x or higher. Upgrade yarn and try again.'); + } + + // Check that `aws` commandline tool is installed + try { + utils.log('Checking that aws CLI tool is installed...'); + await execAsync(`aws help`); + } catch (err) { + throw new Error('You must have `awscli` commandline tool installed. Install it and try again.'); + } + + // Check that `aws` credentials are setup + try { + utils.log('Checking that aws credentials are configured...'); + await execAsync(`aws sts get-caller-identity`); + } catch (err) { + throw new Error('You must setup your AWS credentials by running `aws configure`. Do this and try again.'); + } + + utils.log('Checking that git branch is up to date with upstream...'); + await execAsync('git fetch'); + const res = await execAsync('git status -bs'); // s - short format, b - branch info + /** + * Possible outcomes + * ## branch_name...origin/branch_name [behind n] + * ## branch_name...origin/branch_name [ahead n] + * ## branch_name...origin/branch_name + */ + const gitShortStatusHeader = res.stdout.split('\n')[0]; + if (gitShortStatusHeader.includes('behind')) { + throw new Error('Your branch is behind upstream. Please pull before publishing.'); + } else if (gitShortStatusHeader.includes('ahead')) { + throw new Error('Your branch is ahead of upstream. Please push before publishing.'); + } +} + +checkPublishRequiredSetupAsync().catch(err => { + utils.log(err.message); + process.exit(1); +}); diff --git a/packages/monorepo-scripts/src/publish.ts b/packages/monorepo-scripts/src/publish.ts index 73106821a..36970f85b 100644 --- a/packages/monorepo-scripts/src/publish.ts +++ b/packages/monorepo-scripts/src/publish.ts @@ -13,14 +13,14 @@ import semverDiff = require('semver-diff'); import semverSort = require('semver-sort'); import { constants } from './constants'; -import { Changelog, Changes, PackageToVersionChange, SemVerIndex, UpdatedPackage } from './types'; -import { utils } from './utils'; +import { Changelog, 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 LERNA_EXECUTABLE = './node_modules/lerna/bin/lerna.js'; const semverNameToIndex: { [semver: string]: number } = { patch: SemVerIndex.Patch, minor: SemVerIndex.Minor, @@ -39,11 +39,6 @@ const packageNameToWebsitePath: { [name: string]: string } = { }; (async () => { - const hasRequiredSetup = await checkPublishRequiredSetupAsync(); - if (!hasRequiredSetup) { - return; // abort - } - // Fetch public, updated Lerna packages const shouldIncludePrivate = false; const updatedPublicLernaPackages = await utils.getUpdatedLernaPackagesAsync(shouldIncludePrivate); @@ -114,54 +109,6 @@ package.ts. Please add an entry for it and try again.`, } } -async function checkPublishRequiredSetupAsync(): Promise<boolean> { - // check to see if logged into npm before publishing - try { - // HACK: for some reason on some setups, the `npm whoami` will not recognize a logged-in user - // unless run with `sudo` (i.e Fabio's NVM setup) but is fine for others (Jacob's N setup). - await execAsync(`sudo npm whoami`); - } catch (err) { - utils.log('You must be logged into npm in the commandline to publish. Run `npm login` and try again.'); - return false; - } - - // Check to see if Git personal token setup - if (_.isUndefined(constants.githubPersonalAccessToken)) { - utils.log( - 'You must have a Github personal access token set to an envVar named `GITHUB_PERSONAL_ACCESS_TOKEN_0X_JS`. Add it then try again.', - ); - return false; - } - - // Check Yarn version is 1.X - const result = await execAsync(`yarn --version`); - const version = result.stdout; - const versionSegments = version.split('.'); - const majorVersion = _.parseInt(versionSegments[0]); - if (majorVersion < 1) { - utils.log('Your yarn version must be v1.x or higher. Upgrade yarn and try again.'); - return false; - } - - // Check that `aws` commandline tool is installed - try { - await execAsync(`aws help`); - } catch (err) { - utils.log('You must have `awscli` commandline tool installed. Install it and try again.'); - return false; - } - - // Check that `aws` credentials are setup - try { - await execAsync(`aws sts get-caller-identity`); - } catch (err) { - utils.log('You must setup your AWS credentials by running `aws configure`. Do this and try again.'); - return false; - } - - return true; -} - async function pushChangelogsToGithubAsync(): Promise<void> { await execAsync(`git add . --all`, { cwd: constants.monorepoRootPath }); await execAsync(`git commit -m "Updated CHANGELOGS"`, { cwd: constants.monorepoRootPath }); @@ -175,9 +122,9 @@ async function updateChangeLogsAsync(updatedPublicLernaPackages: LernaPackage[]) const packageName = lernaPackage.package.name; const changelogJSONPath = path.join(lernaPackage.location, 'CHANGELOG.json'); const changelogJSON = utils.getChangelogJSONOrCreateIfMissing(changelogJSONPath); - let changelogs: Changelog[]; + let changelog: Changelog; try { - changelogs = JSON.parse(changelogJSON); + changelog = JSON.parse(changelogJSON); } catch (err) { throw new Error( `${lernaPackage.package.name}'s CHANGELOG.json contains invalid JSON. Please fix and try again.`, @@ -185,11 +132,11 @@ async function updateChangeLogsAsync(updatedPublicLernaPackages: LernaPackage[]) } const currentVersion = lernaPackage.package.version; - const shouldAddNewEntry = shouldAddNewChangelogEntry(currentVersion, changelogs); + const shouldAddNewEntry = changelogUtils.shouldAddNewChangelogEntry(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 newChangelogEntry: VersionChangelog = { timestamp: TODAYS_TIMESTAMP, version: nextPatchVersion, changes: [ @@ -198,27 +145,27 @@ async function updateChangeLogsAsync(updatedPublicLernaPackages: LernaPackage[]) }, ], }; - changelogs = [newChangelogEntry, ...changelogs]; + changelog = [newChangelogEntry, ...changelog]; packageToVersionChange[packageName] = semverDiff(currentVersion, nextPatchVersion); } 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; + changelog[0] = lastEntry; packageToVersionChange[packageName] = semverDiff(currentVersion, lastEntry.version); } // Save updated CHANGELOG.json - fs.writeFileSync(changelogJSONPath, JSON.stringify(changelogs, null, '\t')); + fs.writeFileSync(changelogJSONPath, JSON.stringify(changelog, null, '\t')); await utils.prettifyAsync(changelogJSONPath, constants.monorepoRootPath); utils.log(`${packageName}: Updated CHANGELOG.json`); // Generate updated CHANGELOG.md - const changelogMd = generateChangelogMd(changelogs); + const changelogMd = changelogUtils.generateChangelogMd(changelog); const changelogMdPath = path.join(lernaPackage.location, 'CHANGELOG.md'); fs.writeFileSync(changelogMdPath, changelogMd); await utils.prettifyAsync(changelogMdPath, constants.monorepoRootPath); @@ -231,7 +178,8 @@ async function updateChangeLogsAsync(updatedPublicLernaPackages: LernaPackage[]) 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', '--registry=https://registry.npmjs.org/'], { + const PACKAGE_REGISTRY = 'https://registry.npmjs.org/'; + const child = spawn('lerna', ['publish', `--registry=${PACKAGE_REGISTRY}`], { cwd: constants.monorepoRootPath, }); let shouldPrintOutput = false; @@ -279,46 +227,3 @@ function updateVersionNumberIfNeeded(currentVersion: string, proposedNextVersion } return proposedNextVersion; } - -function shouldAddNewChangelogEntry(currentVersion: string, changelogs: Changelog[]): boolean { - if (_.isEmpty(changelogs)) { - return true; - } - const lastEntry = changelogs[0]; - const isLastEntryCurrentVersion = lastEntry.version === currentVersion; - return isLastEntryCurrentVersion; -} - -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; -} diff --git a/packages/monorepo-scripts/src/remove_tags.ts b/packages/monorepo-scripts/src/remove_tags.ts index 6d09729c7..affdf2751 100644 --- a/packages/monorepo-scripts/src/remove_tags.ts +++ b/packages/monorepo-scripts/src/remove_tags.ts @@ -8,7 +8,7 @@ import semverSort = require('semver-sort'); import { constants } from './constants'; import { Changelog } from './types'; -import { utils } from './utils'; +import { utils } from './utils/utils'; (async () => { const shouldIncludePrivate = true; @@ -24,7 +24,7 @@ import { utils } from './utils'; let latestChangelogVersion: string; if (!_.isUndefined(changelogJSONIfExists)) { - let changelogs: Changelog[]; + let changelogs: Changelog; try { changelogs = JSON.parse(changelogJSONIfExists); } catch (err) { diff --git a/packages/monorepo-scripts/src/test_installation.ts b/packages/monorepo-scripts/src/test_installation.ts index e84221f9d..b67154667 100644 --- a/packages/monorepo-scripts/src/test_installation.ts +++ b/packages/monorepo-scripts/src/test_installation.ts @@ -7,7 +7,7 @@ import * as path from 'path'; import { exec as execAsync } from 'promisify-child-process'; import * as rimraf from 'rimraf'; -import { utils } from './utils'; +import { utils } from './utils/utils'; (async () => { const monorepoRootPath = path.join(__dirname, '../../..'); diff --git a/packages/monorepo-scripts/src/types.ts b/packages/monorepo-scripts/src/types.ts index 28d00d710..36fb923b3 100644 --- a/packages/monorepo-scripts/src/types.ts +++ b/packages/monorepo-scripts/src/types.ts @@ -4,15 +4,17 @@ export interface UpdatedPackage { private: boolean; } -export interface Changes { +export interface Change { note: string; pr?: number; } -export interface Changelog { +export type Changelog = VersionChangelog[]; + +export interface VersionChangelog { timestamp?: number; version: string; - changes: Changes[]; + changes: Change[]; } export enum SemVerIndex { diff --git a/packages/monorepo-scripts/src/utils/changelog_utils.ts b/packages/monorepo-scripts/src/utils/changelog_utils.ts new file mode 100644 index 000000000..edfe65a80 --- /dev/null +++ b/packages/monorepo-scripts/src/utils/changelog_utils.ts @@ -0,0 +1,55 @@ +import * as _ from 'lodash'; +import * as moment from 'moment'; + +import { Change, Changelog, VersionChangelog } from '../types'; + +const CHANGELOG_MD_HEADER = ` +<!-- +This file is auto-generated using the monorepo-scripts package. Don't edit directly. +Edit the package's CHANGELOG.json file only. +--> + +CHANGELOG +`; + +export const changelogUtils = { + getChangelogMdTitle(versionChangelog: VersionChangelog): string { + if (_.isUndefined(versionChangelog.timestamp)) { + throw new Error( + 'All CHANGELOG.json entries must be updated to include a timestamp before generating their MD version', + ); + } + const date = moment(`${versionChangelog.timestamp}`, 'X').format('MMMM D, YYYY'); + const title = `\n## v${versionChangelog.version} - _${date}_\n\n`; + return title; + }, + getChangelogMdChange(change: Change): string { + let line = ` * ${change.note}`; + if (!_.isUndefined(change.pr)) { + line += ` (#${change.pr})`; + } + return line; + }, + generateChangelogMd(changelog: Changelog): string { + let changelogMd = CHANGELOG_MD_HEADER; + _.each(changelog, versionChangelog => { + const title = changelogUtils.getChangelogMdTitle(versionChangelog); + changelogMd += title; + const changelogVersionLines = _.map( + versionChangelog.changes, + changelogUtils.getChangelogMdChange.bind(changelogUtils), + ); + changelogMd += `${_.join(changelogVersionLines, '\n')}`; + }); + + return changelogMd; + }, + shouldAddNewChangelogEntry(currentVersion: string, changelog: Changelog): boolean { + if (_.isEmpty(changelog)) { + return true; + } + const lastEntry = changelog[0]; + const isLastEntryCurrentVersion = lastEntry.version === currentVersion; + return isLastEntryCurrentVersion; + }, +}; diff --git a/packages/monorepo-scripts/src/utils.ts b/packages/monorepo-scripts/src/utils/utils.ts index c2d92c86a..8f2a0bbaa 100644 --- a/packages/monorepo-scripts/src/utils.ts +++ b/packages/monorepo-scripts/src/utils/utils.ts @@ -3,8 +3,8 @@ import lernaGetPackages = require('lerna-get-packages'); import * as _ from 'lodash'; import { exec as execAsync, spawn } from 'promisify-child-process'; -import { constants } from './constants'; -import { UpdatedPackage } from './types'; +import { constants } from '../constants'; +import { UpdatedPackage } from '../types'; export const utils = { log(...args: any[]): void { |