From 797fd38e00e48abddc03be214984f81bf7b1c29c Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Wed, 8 Aug 2018 14:01:12 -0700 Subject: feat(monorepo-scripts): Add confirmation prompt before publishing --- packages/monorepo-scripts/src/publish.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) (limited to 'packages/monorepo-scripts/src') diff --git a/packages/monorepo-scripts/src/publish.ts b/packages/monorepo-scripts/src/publish.ts index 5992131db..6ff0c9bef 100644 --- a/packages/monorepo-scripts/src/publish.ts +++ b/packages/monorepo-scripts/src/publish.ts @@ -31,12 +31,25 @@ const packageNameToWebsitePath: { [name: string]: string } = { 'ethereum-types': 'ethereum-types', }; +async function confirmAsync(message: string): Promise { + 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 () => { // 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); } @@ -107,14 +120,7 @@ package.ts. Please add an entry for it and try again.`, 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); - } + await confirmAsync('Do all the doc pages render properly? (y/n)'); } async function pushChangelogsToGithubAsync(): Promise { -- cgit v1.2.3 From 5ccf41c56693aa45988001260b68fdad2124b12c Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Wed, 8 Aug 2018 14:01:57 -0700 Subject: fix(monorepo-scripts): Fix typo in git tag command --- packages/monorepo-scripts/src/utils/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/monorepo-scripts/src') diff --git a/packages/monorepo-scripts/src/utils/utils.ts b/packages/monorepo-scripts/src/utils/utils.ts index d9bae3ea9..26ac801bd 100644 --- a/packages/monorepo-scripts/src/utils/utils.ts +++ b/packages/monorepo-scripts/src/utils/utils.ts @@ -117,7 +117,7 @@ export const utils = { return tags; }, async getLocalGitTagsAsync(): Promise { - const result = await execAsync(`git tags`, { + const result = await execAsync(`git tag`, { cwd: constants.monorepoRootPath, }); const tagsString = result.stdout; -- cgit v1.2.3 From 283175df9827be38f8cc9f18d3914e3661456fc4 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Mon, 13 Aug 2018 16:49:50 -0700 Subject: Run publish/installation tests in CircleCI (#951) feat(monorepo-scripts): Run publish tests in CircleCI --- packages/monorepo-scripts/src/test_installation.ts | 163 +++++++++++++++------ 1 file changed, 117 insertions(+), 46 deletions(-) (limited to 'packages/monorepo-scripts/src') diff --git a/packages/monorepo-scripts/src/test_installation.ts b/packages/monorepo-scripts/src/test_installation.ts index a8ddf0c58..87c4ad1d7 100644 --- a/packages/monorepo-scripts/src/test_installation.ts +++ b/packages/monorepo-scripts/src/test_installation.ts @@ -2,10 +2,13 @@ import * as fs from 'fs'; import * as _ from 'lodash'; +import * as mkdirp from 'mkdirp'; import * as path from 'path'; import { exec as execAsync } from 'promisify-child-process'; import * as rimraf from 'rimraf'; +import { promisify } from 'util'; +import { Package } from './types'; import { utils } from './utils/utils'; // Packages might not be runnable if they are command-line tools or only run in browsers. @@ -16,64 +19,132 @@ const UNRUNNABLE_PACKAGES = [ '@0xproject/react-docs', ]; +const mkdirpAsync = promisify(mkdirp); +const rimrafAsync = promisify(rimraf); +const writeFileAsync = promisify(fs.writeFile); + +interface PackageErr { + packageName: string; + error: ExecError; +} + +interface ExecError { + message: string; + stack: string; + stderr: string; + stdout: string; +} + +// returns the index for the given package name. +function findPackageIndex(packages: Package[], packageName: string): number { + return _.findIndex(packages, pkg => pkg.packageJson.name === packageName); +} + +function logIfDefined(x: any): void { + if (!_.isUndefined(x)) { + utils.log(x); + } +} + (async () => { const IS_LOCAL_PUBLISH = process.env.IS_LOCAL_PUBLISH === 'true'; const registry = IS_LOCAL_PUBLISH ? 'http://localhost:4873/' : 'https://registry.npmjs.org/'; const monorepoRootPath = path.join(__dirname, '../../..'); - const packages = utils.getTopologicallySortedPackages(monorepoRootPath); + const packages = utils.getPackages(monorepoRootPath); const installablePackages = _.filter( packages, pkg => !pkg.packageJson.private && !_.isUndefined(pkg.packageJson.main) && pkg.packageJson.main.endsWith('.js'), ); utils.log('Testing packages:'); _.map(installablePackages, pkg => utils.log(`* ${pkg.packageJson.name}`)); + // Run all package tests asynchronously and push promises into an array so + // we can wait for all of them to resolve. + const promises: Array> = []; + const errors: PackageErr[] = []; for (const installablePackage of installablePackages) { - const changelogPath = path.join(installablePackage.location, 'CHANGELOG.json'); - const lastChangelogVersion = JSON.parse(fs.readFileSync(changelogPath).toString())[0].version; - const packageName = installablePackage.packageJson.name; - utils.log(`Testing ${packageName}@${lastChangelogVersion}`); - const testDirectory = path.join(monorepoRootPath, '../test-env'); - rimraf.sync(testDirectory); - fs.mkdirSync(testDirectory); - await execAsync('yarn init --yes', { cwd: testDirectory }); - const npmrcFilePath = path.join(testDirectory, '.npmrc'); - fs.writeFileSync(npmrcFilePath, `registry=${registry}`); - utils.log(`Installing ${packageName}@${lastChangelogVersion}`); - await execAsync(`npm install --save ${packageName}@${lastChangelogVersion} --registry=${registry}`, { - cwd: testDirectory, + const packagePromise = testInstallPackageAsync(monorepoRootPath, registry, installablePackage).catch(error => { + errors.push({ packageName: installablePackage.packageJson.name, error }); }); - const indexFilePath = path.join(testDirectory, 'index.ts'); - fs.writeFileSync(indexFilePath, `import * as Package from '${packageName}';\nconsole.log(Package);\n`); - const tsConfig = { - compilerOptions: { - typeRoots: ['node_modules/@0xproject/typescript-typings/types', 'node_modules/@types'], - module: 'commonjs', - target: 'es5', - lib: ['es2017', 'dom'], - declaration: true, - noImplicitReturns: true, - pretty: true, - strict: true, - }, - include: ['index.ts'], - }; - const tsconfigFilePath = path.join(testDirectory, 'tsconfig.json'); - fs.writeFileSync(tsconfigFilePath, JSON.stringify(tsConfig, null, '\t')); - utils.log(`Compiling ${packageName}`); - const tscBinaryPath = path.join(monorepoRootPath, './node_modules/typescript/bin/tsc'); - await execAsync(tscBinaryPath, { cwd: testDirectory }); - utils.log(`Successfully compiled with ${packageName} as a dependency`); - const isUnrunnablePkg = _.includes(UNRUNNABLE_PACKAGES, packageName); - if (!isUnrunnablePkg) { - const transpiledIndexFilePath = path.join(testDirectory, 'index.js'); - utils.log(`Running test script with ${packageName} imported`); - await execAsync(`node ${transpiledIndexFilePath}`); - utils.log(`Successfilly ran test script with ${packageName} imported`); - } - rimraf.sync(testDirectory); + promises.push(packagePromise); + } + await Promise.all(promises); + if (errors.length > 0) { + // We sort error messages according to package topology so that we can + // them in a more intuitive order. E.g. if package A has an error and + // package B imports it, the tests for both package A and package B will + // fail. But package B only fails because of an error in package A. + // Since the error in package A is the root cause, we log it first. + const topologicallySortedPackages = utils.getTopologicallySortedPackages(monorepoRootPath); + const topologicallySortedErrors = _.sortBy(errors, packageErr => + findPackageIndex(topologicallySortedPackages, packageErr.packageName), + ); + _.forEach(topologicallySortedErrors, packageError => { + utils.log(`ERROR in package ${packageError.packageName}:`); + logIfDefined(packageError.error.message); + logIfDefined(packageError.error.stderr); + logIfDefined(packageError.error.stdout); + logIfDefined(packageError.error.stack); + }); + process.exit(0); } })().catch(err => { - utils.log(err.stderr); - utils.log(err.stdout); - process.exit(1); + utils.log(`Unexpected error: ${err.message}`); + process.exit(0); }); + +async function testInstallPackageAsync( + monorepoRootPath: string, + registry: string, + installablePackage: Package, +): Promise { + const changelogPath = path.join(installablePackage.location, 'CHANGELOG.json'); + const lastChangelogVersion = JSON.parse(fs.readFileSync(changelogPath).toString())[0].version; + const packageName = installablePackage.packageJson.name; + utils.log(`Testing ${packageName}@${lastChangelogVersion}`); + const packageDirName = path.join(...(packageName + '-test').split('/')); + const testDirectory = path.join( + monorepoRootPath, + 'packages', + 'monorepo-scripts', + '.installation-test', + packageDirName, + ); + await rimrafAsync(testDirectory); + await mkdirpAsync(testDirectory); + await execAsync('yarn init --yes', { cwd: testDirectory }); + const npmrcFilePath = path.join(testDirectory, '.npmrc'); + await writeFileAsync(npmrcFilePath, `registry=${registry}`); + utils.log(`Installing ${packageName}@${lastChangelogVersion}`); + await execAsync(`npm install --save ${packageName}@${lastChangelogVersion} --registry=${registry}`, { + cwd: testDirectory, + }); + const indexFilePath = path.join(testDirectory, 'index.ts'); + await writeFileAsync(indexFilePath, `import * as Package from '${packageName}';\nconsole.log(Package);\n`); + const tsConfig = { + compilerOptions: { + typeRoots: ['node_modules/@0xproject/typescript-typings/types', 'node_modules/@types'], + module: 'commonjs', + target: 'es5', + lib: ['es2017', 'dom'], + declaration: true, + noImplicitReturns: true, + pretty: true, + strict: true, + }, + include: ['index.ts'], + }; + const tsconfigFilePath = path.join(testDirectory, 'tsconfig.json'); + await writeFileAsync(tsconfigFilePath, JSON.stringify(tsConfig, null, '\t')); + utils.log(`Compiling ${packageName}`); + const tscBinaryPath = path.join(monorepoRootPath, './node_modules/typescript/bin/tsc'); + await execAsync(tscBinaryPath, { cwd: testDirectory }); + utils.log(`Successfully compiled with ${packageName} as a dependency`); + const isUnrunnablePkg = _.includes(UNRUNNABLE_PACKAGES, packageName); + if (!isUnrunnablePkg) { + const transpiledIndexFilePath = path.join(testDirectory, 'index.js'); + utils.log(`Running test script with ${packageName} imported`); + await execAsync(`node ${transpiledIndexFilePath}`); + utils.log(`Successfilly ran test script with ${packageName} imported`); + } + await rimrafAsync(testDirectory); +} -- cgit v1.2.3