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
1 change: 1 addition & 0 deletions packages/aws-cdk/lib/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
return context({
ioHelper,
context: configuration.context,
sourceFiles: configuration.contextSourceFiles,
clear: argv.clear,
json: argv.json,
force: argv.force,
Expand Down
12 changes: 12 additions & 0 deletions packages/aws-cdk/lib/cli/user-configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export class Configuration {
private readonly commandLineContext: Settings;
private _projectConfig?: Settings;
private _projectContext?: Settings;
private contextFileBags: Array<{ fileName: string; bag: Settings }> = [];
private loaded = false;

private ioHelper: IoHelper;
Expand All @@ -144,6 +145,16 @@ export class Configuration {
return this._projectContext;
}

/**
* The names of the context files that currently contain at least one
* context value, in lookup precedence order.
*/
public get contextSourceFiles(): string[] {
return this.contextFileBags
.filter(({ bag }) => Object.keys(bag.all).length > 0)
.map(({ fileName }) => fileName);
}

/**
* Load all config
*/
Expand Down Expand Up @@ -175,6 +186,7 @@ export class Configuration {
}

this.context = new Context(...contextSources);
this.contextFileBags = contextSources.filter((s): s is { fileName: string; bag: Settings } => s.fileName != null);

// Build settings from what's left
const mergedSettings = this.defaultConfig
Expand Down
18 changes: 15 additions & 3 deletions packages/aws-cdk/lib/commands/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ export interface ContextOptions {
*/
readonly context: Context;

/**
* The names of the context files that currently contain context values,
* used to describe where the listed context comes from
*
* @default - no file names are shown in the listing header
*/
readonly sourceFiles?: string[];

/**
* The context key (or its index) to reset
*
Expand Down Expand Up @@ -68,14 +76,14 @@ export async function contextHandler(options: ContextOptions): Promise<number> {
await ioHelper.defaults.result(JSON.stringify(contextValues, undefined, 2));
/* c8 ignore stop */
} else {
await listContext(ioHelper, options.context);
await listContext(ioHelper, options.context, options.sourceFiles ?? []);
}
}

return 0;
}

async function listContext(ioHelper: IoHelper, context: Context) {
async function listContext(ioHelper: IoHelper, context: Context, sourceFiles: string[]) {
const keys = contextKeys(context);

if (keys.length === 0) {
Expand All @@ -94,7 +102,11 @@ async function listContext(ioHelper: IoHelper, context: Context) {
const jsonWithoutNewlines = JSON.stringify(context.all[key], undefined, 2).replace(/\s+/g, ' ');
data_out.push([i, key, jsonWithoutNewlines]);
}
await ioHelper.defaults.info('Context found in %s:', chalk.blue(PROJECT_CONFIG));
if (sourceFiles.length > 0) {
await ioHelper.defaults.info('Context found in %s:', sourceFiles.map((f) => chalk.blue(f)).join(', '));
} else {
await ioHelper.defaults.info('Context found:');
}
await ioHelper.defaults.info('');
await ioHelper.defaults.info(renderTable(data_out, process.stdout.columns));

Expand Down
57 changes: 57 additions & 0 deletions packages/aws-cdk/test/cli/user-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,63 @@ test('load context from all 3 files if available', async () => {
expect(config.context.get('test')).toBe('bar');
});

test('contextSourceFiles lists all files that contain context values', async () => {
// GIVEN
const GIVEN_CONFIG: Map<string, any> = new Map([
[PROJECT_CONFIG, {
context: {
project: 'foobar',
},
}],
[PROJECT_CONTEXT, {
foo: 'bar',
}],
[USER_CONFIG, {
context: {
test: 'bar',
},
}],
]);

// WHEN
mockedFs.pathExists.mockImplementation(path => {
return GIVEN_CONFIG.has(path);
});
mockedFs.readJSON.mockImplementation(((path: string) => {
return GIVEN_CONFIG.get(path);
}) as any);

const config = await Configuration.fromArgsAndFiles(ioHelper);

// THEN
expect(config.contextSourceFiles).toEqual([PROJECT_CONFIG, PROJECT_CONTEXT, '~/.cdk.json']);
});

test('contextSourceFiles does not list files without context values', async () => {
// GIVEN cdk.json has settings but no context, and only cdk.context.json has context
const GIVEN_CONFIG: Map<string, any> = new Map([
[PROJECT_CONFIG, {
project: 'foobar',
}],
[PROJECT_CONTEXT, {
foo: 'bar',
}],
]);

// WHEN
mockedFs.pathExists.mockImplementation(path => {
return GIVEN_CONFIG.has(path);
});
mockedFs.readJSON.mockImplementation(((path: string) => {
return GIVEN_CONFIG.get(path);
}) as any);

const config = await Configuration.fromArgsAndFiles(ioHelper);

// THEN
expect(config.contextSourceFiles).toEqual([PROJECT_CONTEXT]);
});

test('throws an error if the `build` key is specified in the user config', async () => {
// GIVEN
const GIVEN_CONFIG: Map<string, any> = new Map([
Expand Down
42 changes: 42 additions & 0 deletions packages/aws-cdk/test/commands/context-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,48 @@ describe('context --list', () => {
context: configuration.context,
});
});

test('header lists the files the context comes from', async () => {
// GIVEN
ioHost.notifySpy.mockClear();
const configuration = await Configuration.fromArgs(ioHelper);
configuration.context.set('foo', 'bar');

// WHEN
await contextHandler({
ioHelper,
context: configuration.context,
sourceFiles: ['cdk.json', 'cdk.context.json'],
});

// THEN
expect(ioHost.notifySpy).toHaveBeenCalledWith(expect.objectContaining({
message: expect.stringContaining('Context found in'),
}));
const header = ioHost.notifySpy.mock.calls
.map(([msg]) => msg.message as string)
.find((m) => m.includes('Context found in'))!;
expect(header).toContain('cdk.json');
expect(header).toContain('cdk.context.json');
});

test('header does not name any file when the source files are unknown', async () => {
// GIVEN
ioHost.notifySpy.mockClear();
const configuration = await Configuration.fromArgs(ioHelper);
configuration.context.set('foo', 'bar');

// WHEN
await contextHandler({
ioHelper,
context: configuration.context,
});

// THEN
expect(ioHost.notifySpy).toHaveBeenCalledWith(expect.objectContaining({
message: 'Context found:',
}));
});
});

describe('context --reset', () => {
Expand Down
Loading