Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

revert: "feat(cli): glob-style key matching to context --reset (#19840)" #19888

Merged
merged 2 commits into from
Apr 13, 2022
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
80 changes: 10 additions & 70 deletions packages/aws-cdk/lib/commands/context.ts
Original file line number Diff line number Diff line change
@@ -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<number> {
const { configuration, args } = options;

if (args.clear) {
configuration.context.clear();
await configuration.saveContext();
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down
249 changes: 46 additions & 203 deletions packages/aws-cdk/test/commands/context-command.test.ts
Original file line number Diff line number Diff line change
@@ -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',
});

});