diff --git a/packages/aws-cdk/lib/commands/context.ts b/packages/aws-cdk/lib/commands/context.ts index d51b730febe22..21ec2f923e37d 100644 --- a/packages/aws-cdk/lib/commands/context.ts +++ b/packages/aws-cdk/lib/commands/context.ts @@ -1,13 +1,13 @@ import * as chalk from 'chalk'; -import * as minimatch from 'minimatch'; import * as version from '../../lib/version'; import { CommandOptions } from '../command-api'; -import { print, error, warning } from '../logging'; -import { Context, PROJECT_CONFIG, PROJECT_CONTEXT, USER_DEFAULTS } from '../settings'; +import { print } from '../logging'; +import { Context, PROJECT_CONFIG } from '../settings'; import { renderTable } from '../util'; export async function realHandler(options: CommandOptions): Promise { const { configuration, args } = options; + if (args.clear) { configuration.context.clear(); await configuration.saveContext(); @@ -48,8 +48,9 @@ function listContext(context: Context) { const jsonWithoutNewlines = JSON.stringify(context.all[key], undefined, 2).replace(/\s+/g, ' '); data.push([i, key, jsonWithoutNewlines]); } - print('Context found in %s:', chalk.blue(PROJECT_CONFIG)); - print(''); + + print(`Context found in ${chalk.blue(PROJECT_CONFIG)}:\n`); + print(renderTable(data, process.stdout.columns)); // eslint-disable-next-line max-len @@ -62,75 +63,14 @@ function invalidateContext(context: Context, key: string) { // was a number and we fully parsed it. key = keyByNumber(context, i); } + // Unset! if (context.has(key)) { context.unset(key); - // check if the value was actually unset. - if (!context.has(key)) { - print('Context value %s reset. It will be refreshed on next synthesis', chalk.blue(key)); - return; - } - - // Value must be in readonly bag - error('Only context values specified in %s can be reset through the CLI', chalk.blue(PROJECT_CONTEXT)); - throw new Error(`Cannot reset readonly context value with key: ${key}`); - } - - // check if value is expression matching keys - const matches = keysByExpression(context, key); - - if (matches.length > 0) { - - matches.forEach((match) => { - context.unset(match); - }); - - const { unset, readonly } = getUnsetAndReadonly(context, matches); - - // output the reset values - printUnset(unset); - - // warn about values not reset - printReadonly(readonly); - - // throw when none of the matches were reset - if (unset.length === 0) { - throw new Error('None of the matched context values could be reset'); - } - return; + print(`Context value ${chalk.blue(key)} reset. It will be refreshed on next synthesis`); + } else { + print(`No context value with key ${chalk.blue(key)}`); } - - throw new Error(`No context value matching key: ${key}`); -} -function printUnset(unset: string[]) { - if (unset.length === 0) return; - print('The following matched context values reset. They will be refreshed on next synthesis'); - unset.forEach((match) => { - print(' %s', match); - }); -} -function printReadonly(readonly: string[]) { - if (readonly.length === 0) return; - warning('The following matched context values could not be reset through the CLI'); - readonly.forEach((match) => { - print(' %s', match); - }); - print(''); - print('This usually means they are configured in %s or %s', chalk.blue(PROJECT_CONFIG), chalk.blue(USER_DEFAULTS)); -} -function keysByExpression(context: Context, expression: string) { - return context.keys.filter(minimatch.filter(expression)); -} - -function getUnsetAndReadonly(context: Context, matches: string[]) { - return matches.reduce<{ unset: string[], readonly: string[] }>((acc, match) => { - if (context.has(match)) { - acc.readonly.push(match); - } else { - acc.unset.push(match); - } - return acc; - }, { unset: [], readonly: [] }); } function keyByNumber(context: Context, n: number) { diff --git a/packages/aws-cdk/test/commands/context-command.test.ts b/packages/aws-cdk/test/commands/context-command.test.ts index 88023c49aa730..6a73c8008b52e 100644 --- a/packages/aws-cdk/test/commands/context-command.test.ts +++ b/packages/aws-cdk/test/commands/context-command.test.ts @@ -1,221 +1,64 @@ import { realHandler } from '../../lib/commands/context'; -import { Configuration, Settings, Context } from '../../lib/settings'; +import { Configuration } from '../../lib/settings'; -describe('context --list', () => { - test('runs', async() => { - // GIVEN - const configuration = new Configuration(); - configuration.context.set('foo', 'bar'); +test('context list', async() => { + // GIVEN + const configuration = new Configuration(); + configuration.context.set('foo', 'bar'); - expect(configuration.context.all).toEqual({ - foo: 'bar', - }); - - // WHEN - await realHandler({ - configuration, - args: {}, - } as any); - }); -}); - -describe('context --reset', () => { - test('can remove a context key', async () => { - // GIVEN - const configuration = new Configuration(); - configuration.context.set('foo', 'bar'); - configuration.context.set('baz', 'quux'); - - expect(configuration.context.all).toEqual({ - foo: 'bar', - baz: 'quux', - }); - - // WHEN - await realHandler({ - configuration, - args: { reset: 'foo' }, - } as any); - - // THEN - expect(configuration.context.all).toEqual({ - baz: 'quux', - }); - }); - - test('can remove a context key using number', async () => { - // GIVEN - const configuration = new Configuration(); - configuration.context.set('foo', 'bar'); - configuration.context.set('baz', 'quux'); - - expect(configuration.context.all).toEqual({ - foo: 'bar', - baz: 'quux', - }); - - // WHEN - await realHandler({ - configuration, - args: { reset: '1' }, - } as any); - - // THEN - expect(configuration.context.all).toEqual({ - foo: 'bar', - }); - }); - - - test('can reset matched pattern', async () => { - // GIVEN - const configuration = new Configuration(); - configuration.context.set('foo', 'bar'); - configuration.context.set('match-a', 'baz'); - configuration.context.set('match-b', 'qux'); - - expect(configuration.context.all).toEqual({ - 'foo': 'bar', - 'match-a': 'baz', - 'match-b': 'qux', - }); - - // WHEN - await realHandler({ - configuration, - args: { reset: 'match-*' }, - } as any); - - // THEN - expect(configuration.context.all).toEqual({ - foo: 'bar', - }); + expect(configuration.context.all).toEqual({ + foo: 'bar', }); + // WHEN + await realHandler({ + configuration, + args: {}, + } as any); +}); - test('prefers an exact match', async () => { - // GIVEN - const configuration = new Configuration(); - configuration.context.set('foo', 'bar'); - configuration.context.set('fo*', 'baz'); - - expect(configuration.context.all).toEqual({ - 'foo': 'bar', - 'fo*': 'baz', - }); - - // WHEN - await realHandler({ - configuration, - args: { reset: 'fo*' }, - } as any); - - // THEN - expect(configuration.context.all).toEqual({ - foo: 'bar', - }); - }); - - - test('doesn\'t throw when at least one match is reset', async () => { - // GIVEN - const configuration = new Configuration(); - const readOnlySettings = new Settings({ - 'foo': 'bar', - 'match-a': 'baz', - }, true); - configuration.context = new Context(readOnlySettings, new Settings()); - configuration.context.set('match-b', 'quux'); - - // When - await expect(realHandler({ - configuration, - args: { reset: 'match-*' }, - } as any)); +test('context reset can remove a context key', async () => { + // GIVEN + const configuration = new Configuration(); + configuration.context.set('foo', 'bar'); + configuration.context.set('baz', 'quux'); - // Then - expect(configuration.context.all).toEqual({ - 'foo': 'bar', - 'match-a': 'baz', - }); + expect(configuration.context.all).toEqual({ + foo: 'bar', + baz: 'quux', }); - test('throws when key not found', async () => { - // GIVEN - const configuration = new Configuration(); - configuration.context.set('foo', 'bar'); - - expect(configuration.context.all).toEqual({ - foo: 'bar', - }); + // WHEN + await realHandler({ + configuration, + args: { reset: 'foo' }, + } as any); - // THEN - await expect(realHandler({ - configuration, - args: { reset: 'baz' }, - } as any)).rejects.toThrow(/No context value matching key/); + // THEN + expect(configuration.context.all).toEqual({ + baz: 'quux', }); +}); +test('context reset can remove a context key using number', async () => { + // GIVEN + const configuration = new Configuration(); + configuration.context.set('foo', 'bar'); + configuration.context.set('baz', 'quux'); - test('throws when no key of index found', async () => { - // GIVEN - const configuration = new Configuration(); - configuration.context.set('foo', 'bar'); - - expect(configuration.context.all).toEqual({ - foo: 'bar', - }); - - // THEN - await expect(realHandler({ - configuration, - args: { reset: '2' }, - } as any)).rejects.toThrow(/No context key with number/); - }); - - - test('throws when resetting read-only values', async () => { - // GIVEN - const configuration = new Configuration(); - const readOnlySettings = new Settings({ - foo: 'bar', - }, true); - configuration.context = new Context(readOnlySettings); - - expect(configuration.context.all).toEqual({ - foo: 'bar', - }); - - // THEN - await expect(realHandler({ - configuration, - args: { reset: 'foo' }, - } as any)).rejects.toThrow(/Cannot reset readonly context value with key/); + expect(configuration.context.all).toEqual({ + foo: 'bar', + baz: 'quux', }); + // WHEN + await realHandler({ + configuration, + args: { reset: '1' }, + } as any); - test('throws when no matches could be reset', async () => { - // GIVEN - const configuration = new Configuration(); - const readOnlySettings = new Settings({ - 'foo': 'bar', - 'match-a': 'baz', - 'match-b': 'quux', - }, true); - configuration.context = new Context(readOnlySettings); - - expect(configuration.context.all).toEqual({ - 'foo': 'bar', - 'match-a': 'baz', - 'match-b': 'quux', - }); - - // THEN - await expect(realHandler({ - configuration, - args: { reset: 'match-*' }, - } as any)).rejects.toThrow(/None of the matched context values could be reset/); + // THEN + expect(configuration.context.all).toEqual({ + foo: 'bar', }); - }); -