From 16429257ce0118b4270fc1275036d259809a1811 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 26 Apr 2019 11:48:21 +0200 Subject: [PATCH] feat(cdk-test): check API compatibility (#2356) Add a check to `cdk-test` to confirm that no breaking API changes have been introduced with respect to the most recently published version. Because this will incur an NPM fetch, add a flag (`--quick`/`-q`) to disable it. Addresses #145. --- tools/cdk-build-tools/bin/cdk-build.ts | 4 +- tools/cdk-build-tools/bin/cdk-package.ts | 4 +- tools/cdk-build-tools/bin/cdk-test.ts | 28 +++++++-- tools/cdk-build-tools/lib/compile.ts | 8 +-- tools/cdk-build-tools/lib/os.ts | 17 +++++- tools/cdk-build-tools/package-lock.json | 74 +++++++++++++++++++----- tools/cdk-build-tools/package.json | 2 + 7 files changed, 108 insertions(+), 29 deletions(-) diff --git a/tools/cdk-build-tools/bin/cdk-build.ts b/tools/cdk-build-tools/bin/cdk-build.ts index f8eee22bbcf81..bc15cd0b2bf7e 100644 --- a/tools/cdk-build-tools/bin/cdk-build.ts +++ b/tools/cdk-build-tools/bin/cdk-build.ts @@ -28,7 +28,7 @@ async function main() { const options = cdkBuildOptions(); if (options.pre) { - await shell(options.pre, timers); + await shell(options.pre, { timers }); } // See if we need to call cfn2ts @@ -37,7 +37,7 @@ async function main() { // There can be multiple scopes, ensuring it's always an array. options.cloudformation = [options.cloudformation]; } - await shell(['cfn2ts', ...options.cloudformation.map(scope => `--scope=${scope}`)], timers); + await shell(['cfn2ts', ...options.cloudformation.map(scope => `--scope=${scope}`)], { timers }); } await compileCurrentPackage(timers, { jsii: args.jsii, tsc: args.tsc, tslint: args.tslint }); diff --git a/tools/cdk-build-tools/bin/cdk-package.ts b/tools/cdk-build-tools/bin/cdk-package.ts index a7ce99fa4671b..7886faf82fe17 100644 --- a/tools/cdk-build-tools/bin/cdk-package.ts +++ b/tools/cdk-build-tools/bin/cdk-package.ts @@ -36,10 +36,10 @@ async function main() { args.verbose ? '-vvv' : '-v', ...args.targets ? flatMap(args.targets, (target: string) => ['-t', target]) : [], '-o', outdir ]; - await shell(command, timers); + await shell(command, { timers }); } else { // just "npm pack" and deploy to "outdir" - const tarball = (await shell([ 'npm', 'pack' ], timers)).trim(); + const tarball = (await shell([ 'npm', 'pack' ], { timers })).trim(); const target = path.join(outdir, 'js'); await fs.remove(target); await fs.mkdirp(target); diff --git a/tools/cdk-build-tools/bin/cdk-test.ts b/tools/cdk-build-tools/bin/cdk-test.ts index 71da1079617cf..d9068029aa9f7 100644 --- a/tools/cdk-build-tools/bin/cdk-test.ts +++ b/tools/cdk-build-tools/bin/cdk-test.ts @@ -9,7 +9,13 @@ async function main() { const args = yargs .env('CDK_TEST') .usage('Usage: cdk-test') - .option('force', { type: 'boolean', alias: 'f', desc: 'Force a rebuild' }) + .option('quick', { type: 'boolean', alias: 'q', desc: `Skip slow tests`, default: false }) + .option('jsii-diff', { + type: 'string', + desc: 'Specify a different jsii-diff executable', + default: require.resolve('jsii-diff/bin/jsii-diff'), + defaultDescription: 'jsii-diff provided by node dependencies' + }) .option('jest', { type: 'string', desc: 'Specify a different jest executable', @@ -33,7 +39,7 @@ async function main() { const options = cdkBuildOptions(); if (options.test) { - await shell(options.test, timers); + await shell(options.test, { timers }); } const testFiles = await unitTestFiles(); @@ -43,7 +49,7 @@ async function main() { if (testFiles.length > 0) { throw new Error(`Jest is enabled, but ${testFiles.length} nodeunit tests were found!`); } - await shell([args.jest, '--testEnvironment=node', '--coverage'], timers); + await shell([args.jest, '--testEnvironment=node', '--coverage'], { timers }); } else if (testFiles.length > 0) { const testCommand: string[] = []; @@ -67,12 +73,24 @@ async function main() { testCommand.push(args.nodeunit); testCommand.push(...testFiles.map(f => f.path)); - await shell(testCommand, timers); + await shell(testCommand, { timers }); } // Run integration test if the package has integ test files if (await hasIntegTests()) { - await shell(['cdk-integ-assert'], timers); + await shell(['cdk-integ-assert'], { timers }); + } + + // Run compatibility check if not disabled (against the latest + // published version) + if (!args.quick) { + try { + await shell([args["jsii-diff"], 'npm:'], { timers }); + } catch (e) { + // If there was an exception running jsii-diff, swallow it + process.stderr.write(`The package seems to have undergone breaking API changes. Please revise and try to avoid.\n`); + process.stderr.write(`(This is just a warning for now but will soon become a build failure.)\n`); + } } } diff --git a/tools/cdk-build-tools/lib/compile.ts b/tools/cdk-build-tools/lib/compile.ts index ef8a1bbed0ab6..955248bc2f51e 100644 --- a/tools/cdk-build-tools/lib/compile.ts +++ b/tools/cdk-build-tools/lib/compile.ts @@ -7,7 +7,7 @@ import { Timers } from "./timer"; * Run the compiler on the current package */ export async function compileCurrentPackage(timers: Timers, compilers: CompilerOverrides = {}): Promise { - const stdout = await shell(packageCompiler(compilers), timers); + const stdout = await shell(packageCompiler(compilers), { timers }); // WORKAROUND: jsii 0.8.2 does not exit with non-zero on compilation errors // until this is released: https://github.com/awslabs/jsii/pull/442 @@ -22,7 +22,7 @@ export async function compileCurrentPackage(timers: Timers, compilers: CompilerO } // Always call linters - await shell([compilers.tslint || require.resolve('tslint/bin/tslint'), '--project', '.'], timers); - await shell(['pkglint'], timers); - await shell([ path.join(__dirname, '..', 'bin', 'cdk-awslint') ], timers); + await shell([compilers.tslint || require.resolve('tslint/bin/tslint'), '--project', '.'], { timers }); + await shell(['pkglint'], { timers }); + await shell([ path.join(__dirname, '..', 'bin', 'cdk-awslint') ], { timers }); } diff --git a/tools/cdk-build-tools/lib/os.ts b/tools/cdk-build-tools/lib/os.ts index a36a26fd95c78..a65b94a7bd15f 100644 --- a/tools/cdk-build-tools/lib/os.ts +++ b/tools/cdk-build-tools/lib/os.ts @@ -1,24 +1,31 @@ import child_process = require("child_process"); +import colors = require('colors/safe'); import fs = require('fs'); import util = require('util'); import { Timers } from "./timer"; +interface ShellOptions { + timers?: Timers; +} + /** * A shell command that does what you want * * Is platform-aware, handles errors nicely. */ -export async function shell(command: string[], timers?: Timers): Promise { - const timer = (timers || new Timers()).start(command[0]); +export async function shell(command: string[], options: ShellOptions = {}): Promise { + const timer = (options.timers || new Timers()).start(command[0]); await makeShellScriptExecutable(command[0]); const child = child_process.spawn(command[0], command.slice(1), { // Need this for Windows where we want .cmd and .bat to be found as well. shell: true, - stdio: [ 'ignore', 'pipe', 'inherit' ] + stdio: [ 'ignore', 'pipe', 'pipe' ] }); + const makeRed = process.stderr.isTTY ? colors.red : (x: string) => x; + return new Promise((resolve, reject) => { const stdout = new Array(); @@ -27,6 +34,10 @@ export async function shell(command: string[], timers?: Timers): Promise stdout.push(chunk); }); + child.stderr!.on('data', chunk => { + process.stderr.write(makeRed(chunk.toString())); + }); + child.once('error', reject); child.once('exit', code => { diff --git a/tools/cdk-build-tools/package-lock.json b/tools/cdk-build-tools/package-lock.json index b18e322823e67..0e2acbcb30750 100644 --- a/tools/cdk-build-tools/package-lock.json +++ b/tools/cdk-build-tools/package-lock.json @@ -1,6 +1,6 @@ { "name": "cdk-build-tools", - "version": "0.28.0", + "version": "0.29.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1719,7 +1719,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -1737,11 +1738,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1754,15 +1757,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1865,7 +1871,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -1875,6 +1882,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1887,17 +1895,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -1914,6 +1925,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -1986,7 +1998,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -1996,6 +2009,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2071,7 +2085,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2101,6 +2116,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2118,6 +2134,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2156,11 +2173,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } }, @@ -3203,6 +3222,18 @@ } } }, + "jsii-diff": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/jsii-diff/-/jsii-diff-0.10.3.tgz", + "integrity": "sha512-iUen3xsTL7fi8mtv1wb+r7VWGRCmM/PeIHgUiWX65ber+sli9nomBJFrSwi9AqwTJlAnXe7Tzax1OaIr7rTIGg==", + "requires": { + "jsii-reflect": "^0.10.3", + "jsii-spec": "^0.10.3", + "log4js": "^4.1.0", + "typescript": "^3.4.3", + "yargs": "^13.2.2" + } + }, "jsii-pacmak": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/jsii-pacmak/-/jsii-pacmak-0.10.3.tgz", @@ -3226,6 +3257,18 @@ } } }, + "jsii-reflect": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/jsii-reflect/-/jsii-reflect-0.10.3.tgz", + "integrity": "sha512-YGWd/IkNG+RZVxRLqK7ionTIIJ5CY9hHsB/h9fqFOQ9erEiT0FZ/Y92Ln1/3jdH3NsGEJFbHqcRnl4pazuQO0A==", + "requires": { + "colors": "^1.3.3", + "fs-extra": "^7.0.1", + "jsii-spec": "^0.10.3", + "oo-ascii-tree": "^0.10.3", + "yargs": "^13.2.2" + } + }, "jsii-spec": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/jsii-spec/-/jsii-spec-0.10.3.tgz", @@ -3744,7 +3787,7 @@ "dependencies": { "resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "resolved": "", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, "yargs-parser": { @@ -3829,6 +3872,11 @@ "wrappy": "1" } }, + "oo-ascii-tree": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/oo-ascii-tree/-/oo-ascii-tree-0.10.3.tgz", + "integrity": "sha512-Lkh6N+jY244GxCh3f5TqQOrzykmnIKp4EN2BdcFLlkKCUhr8rGIEtI3gGdnK78omnD3+tIES9s+RZFZtFA/I6Q==" + }, "opener": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index 2b8f16e739bfd..833835520f983 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -35,9 +35,11 @@ }, "dependencies": { "awslint": "^0.29.0", + "colors": "^1.3.3", "fs-extra": "^7.0.1", "jest": "^24.7.1", "jsii": "^0.10.3", + "jsii-diff": "^0.10.3", "jsii-pacmak": "^0.10.3", "nodeunit": "^0.11.3", "nyc": "^14.0.0",