Skip to content

Commit 1642925

Browse files
authored
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.
1 parent c8a19e5 commit 1642925

File tree

7 files changed

+108
-29
lines changed

7 files changed

+108
-29
lines changed

tools/cdk-build-tools/bin/cdk-build.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ async function main() {
2828
const options = cdkBuildOptions();
2929

3030
if (options.pre) {
31-
await shell(options.pre, timers);
31+
await shell(options.pre, { timers });
3232
}
3333

3434
// See if we need to call cfn2ts
@@ -37,7 +37,7 @@ async function main() {
3737
// There can be multiple scopes, ensuring it's always an array.
3838
options.cloudformation = [options.cloudformation];
3939
}
40-
await shell(['cfn2ts', ...options.cloudformation.map(scope => `--scope=${scope}`)], timers);
40+
await shell(['cfn2ts', ...options.cloudformation.map(scope => `--scope=${scope}`)], { timers });
4141
}
4242

4343
await compileCurrentPackage(timers, { jsii: args.jsii, tsc: args.tsc, tslint: args.tslint });

tools/cdk-build-tools/bin/cdk-package.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ async function main() {
3636
args.verbose ? '-vvv' : '-v',
3737
...args.targets ? flatMap(args.targets, (target: string) => ['-t', target]) : [],
3838
'-o', outdir ];
39-
await shell(command, timers);
39+
await shell(command, { timers });
4040
} else {
4141
// just "npm pack" and deploy to "outdir"
42-
const tarball = (await shell([ 'npm', 'pack' ], timers)).trim();
42+
const tarball = (await shell([ 'npm', 'pack' ], { timers })).trim();
4343
const target = path.join(outdir, 'js');
4444
await fs.remove(target);
4545
await fs.mkdirp(target);

tools/cdk-build-tools/bin/cdk-test.ts

+23-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ async function main() {
99
const args = yargs
1010
.env('CDK_TEST')
1111
.usage('Usage: cdk-test')
12-
.option('force', { type: 'boolean', alias: 'f', desc: 'Force a rebuild' })
12+
.option('quick', { type: 'boolean', alias: 'q', desc: `Skip slow tests`, default: false })
13+
.option('jsii-diff', {
14+
type: 'string',
15+
desc: 'Specify a different jsii-diff executable',
16+
default: require.resolve('jsii-diff/bin/jsii-diff'),
17+
defaultDescription: 'jsii-diff provided by node dependencies'
18+
})
1319
.option('jest', {
1420
type: 'string',
1521
desc: 'Specify a different jest executable',
@@ -33,7 +39,7 @@ async function main() {
3339
const options = cdkBuildOptions();
3440

3541
if (options.test) {
36-
await shell(options.test, timers);
42+
await shell(options.test, { timers });
3743
}
3844

3945
const testFiles = await unitTestFiles();
@@ -43,7 +49,7 @@ async function main() {
4349
if (testFiles.length > 0) {
4450
throw new Error(`Jest is enabled, but ${testFiles.length} nodeunit tests were found!`);
4551
}
46-
await shell([args.jest, '--testEnvironment=node', '--coverage'], timers);
52+
await shell([args.jest, '--testEnvironment=node', '--coverage'], { timers });
4753
} else if (testFiles.length > 0) {
4854
const testCommand: string[] = [];
4955

@@ -67,12 +73,24 @@ async function main() {
6773
testCommand.push(args.nodeunit);
6874
testCommand.push(...testFiles.map(f => f.path));
6975

70-
await shell(testCommand, timers);
76+
await shell(testCommand, { timers });
7177
}
7278

7379
// Run integration test if the package has integ test files
7480
if (await hasIntegTests()) {
75-
await shell(['cdk-integ-assert'], timers);
81+
await shell(['cdk-integ-assert'], { timers });
82+
}
83+
84+
// Run compatibility check if not disabled (against the latest
85+
// published version)
86+
if (!args.quick) {
87+
try {
88+
await shell([args["jsii-diff"], 'npm:'], { timers });
89+
} catch (e) {
90+
// If there was an exception running jsii-diff, swallow it
91+
process.stderr.write(`The package seems to have undergone breaking API changes. Please revise and try to avoid.\n`);
92+
process.stderr.write(`(This is just a warning for now but will soon become a build failure.)\n`);
93+
}
7694
}
7795
}
7896

tools/cdk-build-tools/lib/compile.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Timers } from "./timer";
77
* Run the compiler on the current package
88
*/
99
export async function compileCurrentPackage(timers: Timers, compilers: CompilerOverrides = {}): Promise<void> {
10-
const stdout = await shell(packageCompiler(compilers), timers);
10+
const stdout = await shell(packageCompiler(compilers), { timers });
1111

1212
// WORKAROUND: jsii 0.8.2 does not exit with non-zero on compilation errors
1313
// until this is released: https://github.com/awslabs/jsii/pull/442
@@ -22,7 +22,7 @@ export async function compileCurrentPackage(timers: Timers, compilers: CompilerO
2222
}
2323

2424
// Always call linters
25-
await shell([compilers.tslint || require.resolve('tslint/bin/tslint'), '--project', '.'], timers);
26-
await shell(['pkglint'], timers);
27-
await shell([ path.join(__dirname, '..', 'bin', 'cdk-awslint') ], timers);
25+
await shell([compilers.tslint || require.resolve('tslint/bin/tslint'), '--project', '.'], { timers });
26+
await shell(['pkglint'], { timers });
27+
await shell([ path.join(__dirname, '..', 'bin', 'cdk-awslint') ], { timers });
2828
}

tools/cdk-build-tools/lib/os.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11
import child_process = require("child_process");
2+
import colors = require('colors/safe');
23
import fs = require('fs');
34
import util = require('util');
45
import { Timers } from "./timer";
56

7+
interface ShellOptions {
8+
timers?: Timers;
9+
}
10+
611
/**
712
* A shell command that does what you want
813
*
914
* Is platform-aware, handles errors nicely.
1015
*/
11-
export async function shell(command: string[], timers?: Timers): Promise<string> {
12-
const timer = (timers || new Timers()).start(command[0]);
16+
export async function shell(command: string[], options: ShellOptions = {}): Promise<string> {
17+
const timer = (options.timers || new Timers()).start(command[0]);
1318

1419
await makeShellScriptExecutable(command[0]);
1520

1621
const child = child_process.spawn(command[0], command.slice(1), {
1722
// Need this for Windows where we want .cmd and .bat to be found as well.
1823
shell: true,
19-
stdio: [ 'ignore', 'pipe', 'inherit' ]
24+
stdio: [ 'ignore', 'pipe', 'pipe' ]
2025
});
2126

27+
const makeRed = process.stderr.isTTY ? colors.red : (x: string) => x;
28+
2229
return new Promise<string>((resolve, reject) => {
2330
const stdout = new Array<any>();
2431

@@ -27,6 +34,10 @@ export async function shell(command: string[], timers?: Timers): Promise<string>
2734
stdout.push(chunk);
2835
});
2936

37+
child.stderr!.on('data', chunk => {
38+
process.stderr.write(makeRed(chunk.toString()));
39+
});
40+
3041
child.once('error', reject);
3142

3243
child.once('exit', code => {

tools/cdk-build-tools/package-lock.json

+61-13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/cdk-build-tools/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@
3535
},
3636
"dependencies": {
3737
"awslint": "^0.29.0",
38+
"colors": "^1.3.3",
3839
"fs-extra": "^7.0.1",
3940
"jest": "^24.7.1",
4041
"jsii": "^0.10.3",
42+
"jsii-diff": "^0.10.3",
4143
"jsii-pacmak": "^0.10.3",
4244
"nodeunit": "^0.11.3",
4345
"nyc": "^14.0.0",

0 commit comments

Comments
 (0)