Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { integTest, withDefaultFixture } from '../../../lib';

integTest(
'cdk ls --json in CI does not print synthesis time to stdout',
withDefaultFixture(async (fixture) => {
// `cdk ls --json` stdout is a machine-readable contract and is often piped (e.g. to `jq`),
// so it must be only the stack listing, not status lines like "✨ Synthesis time: ...".
const listing = await fixture.cdk(['ls', '--json'], {
verbose: false, // fixture defaults verbose on; turn it off so stdout is just the listing
captureStderr: false, // capture stdout only; stderr is folded into the result by default
modEnv: { CI: 'true' }, // CI routes non-error output to stdout (default is stderr)
});

const lines = listing.trim().split('\n').filter(line => line.length > 0);

// every line should be a stack; a synth-time line would not carry the prefix
expect(lines.length).toBeGreaterThan(0);
for (const line of lines) {
expect(line).toContain(fixture.stackNamePrefix);
}
}),
);
5 changes: 5 additions & 0 deletions packages/aws-cdk/lib/cli/cdk-toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,11 @@ export class CdkToolkit {
): Promise<number> {
this.ioHost.rewriteOnce(IO.CDK_TOOLKIT_I2901, (msg) => formatStackList(msg.data.stacks, options));

// With `--json`, stdout must stay machine-parsable, so suppress the synth-time line (I1000).
if (options.json) {
this.ioHost.once(IO.CDK_TOOLKIT_I1000, () => ({ preventDefault: true }));
}

await this.toolkit.list(this.props.cloudExecutable, {
stacks: selectors.length > 0
? { patterns: selectors, strategy: StackSelectionStrategy.PATTERN_MATCH, expand: ExpandStackSelection.UPSTREAM }
Expand Down
29 changes: 29 additions & 0 deletions packages/aws-cdk/test/cli/cdk-toolkit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,35 @@ describe('list', () => {
'Test-Stack-B',
]);
});

test('with --json, suppresses the synthesis-time line so stdout stays machine-parsable', async () => {
// `cdk ls --json` stdout is a machine-readable contract; the "Synthesis time" line
// (CDK_TOOLKIT_I1000) must not be written (in CI mode non-error output goes to stdout).
const toolkit = defaultToolkitSetup();
const onceSpy = jest.spyOn(ioHost, 'once');

// WHEN
await toolkit.list([], { json: true });

// THEN - a one-shot suppressor for I1000 was registered that prevents default handling.
const i1000Call = onceSpy.mock.calls.find(([code]) => (code as any)?.code === 'CDK_TOOLKIT_I1000');
expect(i1000Call).toBeDefined();
const listener = i1000Call![1] as (msg: any) => any;
expect(listener({ code: 'CDK_TOOLKIT_I1000' })).toEqual({ preventDefault: true });
});

test('without --json, does not suppress the synthesis-time line', async () => {
// Plain `cdk ls` is not a machine-readable contract, so the line is left alone.
const toolkit = defaultToolkitSetup();
const onceSpy = jest.spyOn(ioHost, 'once');

// WHEN
await toolkit.list([]);

// THEN
const i1000Call = onceSpy.mock.calls.find(([code]) => (code as any)?.code === 'CDK_TOOLKIT_I1000');
expect(i1000Call).toBeUndefined();
});
});

describe('deploy', () => {
Expand Down
Loading