Skip to content

Commit 39a7c1f

Browse files
authored
feat(cli): --force flag and glob-style key matches for context --reset (#19890)
New pull request for glob style key matches in context --reset (#19840) Adds the `--force` flag to ignore missing key errors. This is in response to #19888 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent f15cb27 commit 39a7c1f

File tree

3 files changed

+295
-58
lines changed

3 files changed

+295
-58
lines changed

packages/aws-cdk/lib/cli.ts

+1
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ async function parseCommandLineArguments() {
238238
)
239239
.command('context', 'Manage cached context values', (yargs: Argv) => yargs
240240
.option('reset', { alias: 'e', desc: 'The context key (or its index) to reset', type: 'string', requiresArg: true })
241+
.option('force', { alias: 'f', desc: 'Ignore missing key error', type: 'boolean', default: false })
241242
.option('clear', { desc: 'Clear all context', type: 'boolean' }))
242243
.command(['docs', 'doc'], 'Opens the reference documentation in a browser', (yargs: Argv) => yargs
243244
.option('browser', {

packages/aws-cdk/lib/commands/context.ts

+75-12
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import * as chalk from 'chalk';
2+
import * as minimatch from 'minimatch';
23
import * as version from '../../lib/version';
34
import { CommandOptions } from '../command-api';
4-
import { print } from '../logging';
5-
import { Context, PROJECT_CONFIG } from '../settings';
5+
import { print, error, warning } from '../logging';
6+
import { Context, PROJECT_CONFIG, PROJECT_CONTEXT, USER_DEFAULTS } from '../settings';
67
import { renderTable } from '../util';
78

89
export async function realHandler(options: CommandOptions): Promise<number> {
910
const { configuration, args } = options;
10-
1111
if (args.clear) {
1212
configuration.context.clear();
1313
await configuration.saveContext();
1414
print('All context values cleared.');
1515
} else if (args.reset) {
16-
invalidateContext(configuration.context, args.reset as string);
16+
invalidateContext(configuration.context, args.reset as string, args.force as boolean);
1717
await configuration.saveContext();
1818
} else {
1919
// List -- support '--json' flag
@@ -48,30 +48,93 @@ function listContext(context: Context) {
4848
const jsonWithoutNewlines = JSON.stringify(context.all[key], undefined, 2).replace(/\s+/g, ' ');
4949
data.push([i, key, jsonWithoutNewlines]);
5050
}
51-
52-
print(`Context found in ${chalk.blue(PROJECT_CONFIG)}:\n`);
53-
51+
print('Context found in %s:', chalk.blue(PROJECT_CONFIG));
52+
print('');
5453
print(renderTable(data, process.stdout.columns));
5554

5655
// eslint-disable-next-line max-len
5756
print(`Run ${chalk.blue('cdk context --reset KEY_OR_NUMBER')} to remove a context key. It will be refreshed on the next CDK synthesis run.`);
5857
}
5958

60-
function invalidateContext(context: Context, key: string) {
59+
function invalidateContext(context: Context, key: string, force: boolean) {
6160
const i = parseInt(key, 10);
6261
if (`${i}` === key) {
6362
// was a number and we fully parsed it.
6463
key = keyByNumber(context, i);
6564
}
66-
6765
// Unset!
6866
if (context.has(key)) {
6967
context.unset(key);
70-
print(`Context value ${chalk.blue(key)} reset. It will be refreshed on next synthesis`);
71-
} else {
72-
print(`No context value with key ${chalk.blue(key)}`);
68+
// check if the value was actually unset.
69+
if (!context.has(key)) {
70+
print('Context value %s reset. It will be refreshed on next synthesis', chalk.blue(key));
71+
return;
72+
}
73+
74+
// Value must be in readonly bag
75+
error('Only context values specified in %s can be reset through the CLI', chalk.blue(PROJECT_CONTEXT));
76+
if (!force) {
77+
throw new Error(`Cannot reset readonly context value with key: ${key}`);
78+
}
79+
}
80+
81+
// check if value is expression matching keys
82+
const matches = keysByExpression(context, key);
83+
84+
if (matches.length > 0) {
85+
86+
matches.forEach((match) => {
87+
context.unset(match);
88+
});
89+
90+
const { unset, readonly } = getUnsetAndReadonly(context, matches);
91+
92+
// output the reset values
93+
printUnset(unset);
94+
95+
// warn about values not reset
96+
printReadonly(readonly);
97+
98+
// throw when none of the matches were reset
99+
if (!force && unset.length === 0) {
100+
throw new Error('None of the matched context values could be reset');
101+
}
102+
return;
103+
}
104+
if (!force) {
105+
throw new Error(`No context value matching key: ${key}`);
73106
}
74107
}
108+
function printUnset(unset: string[]) {
109+
if (unset.length === 0) return;
110+
print('The following matched context values reset. They will be refreshed on next synthesis');
111+
unset.forEach((match) => {
112+
print(' %s', match);
113+
});
114+
}
115+
function printReadonly(readonly: string[]) {
116+
if (readonly.length === 0) return;
117+
warning('The following matched context values could not be reset through the CLI');
118+
readonly.forEach((match) => {
119+
print(' %s', match);
120+
});
121+
print('');
122+
print('This usually means they are configured in %s or %s', chalk.blue(PROJECT_CONFIG), chalk.blue(USER_DEFAULTS));
123+
}
124+
function keysByExpression(context: Context, expression: string) {
125+
return context.keys.filter(minimatch.filter(expression));
126+
}
127+
128+
function getUnsetAndReadonly(context: Context, matches: string[]) {
129+
return matches.reduce<{ unset: string[], readonly: string[] }>((acc, match) => {
130+
if (context.has(match)) {
131+
acc.readonly.push(match);
132+
} else {
133+
acc.unset.push(match);
134+
}
135+
return acc;
136+
}, { unset: [], readonly: [] });
137+
}
75138

76139
function keyByNumber(context: Context, n: number) {
77140
for (const [i, key] of contextKeys(context)) {

0 commit comments

Comments
 (0)