|
1 | 1 | import * as chalk from 'chalk';
|
| 2 | +import * as minimatch from 'minimatch'; |
2 | 3 | import * as version from '../../lib/version';
|
3 | 4 | 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'; |
6 | 7 | import { renderTable } from '../util';
|
7 | 8 |
|
8 | 9 | export async function realHandler(options: CommandOptions): Promise<number> {
|
9 | 10 | const { configuration, args } = options;
|
10 |
| - |
11 | 11 | if (args.clear) {
|
12 | 12 | configuration.context.clear();
|
13 | 13 | await configuration.saveContext();
|
14 | 14 | print('All context values cleared.');
|
15 | 15 | } else if (args.reset) {
|
16 |
| - invalidateContext(configuration.context, args.reset as string); |
| 16 | + invalidateContext(configuration.context, args.reset as string, args.force as boolean); |
17 | 17 | await configuration.saveContext();
|
18 | 18 | } else {
|
19 | 19 | // List -- support '--json' flag
|
@@ -48,30 +48,93 @@ function listContext(context: Context) {
|
48 | 48 | const jsonWithoutNewlines = JSON.stringify(context.all[key], undefined, 2).replace(/\s+/g, ' ');
|
49 | 49 | data.push([i, key, jsonWithoutNewlines]);
|
50 | 50 | }
|
51 |
| - |
52 |
| - print(`Context found in ${chalk.blue(PROJECT_CONFIG)}:\n`); |
53 |
| - |
| 51 | + print('Context found in %s:', chalk.blue(PROJECT_CONFIG)); |
| 52 | + print(''); |
54 | 53 | print(renderTable(data, process.stdout.columns));
|
55 | 54 |
|
56 | 55 | // eslint-disable-next-line max-len
|
57 | 56 | 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.`);
|
58 | 57 | }
|
59 | 58 |
|
60 |
| -function invalidateContext(context: Context, key: string) { |
| 59 | +function invalidateContext(context: Context, key: string, force: boolean) { |
61 | 60 | const i = parseInt(key, 10);
|
62 | 61 | if (`${i}` === key) {
|
63 | 62 | // was a number and we fully parsed it.
|
64 | 63 | key = keyByNumber(context, i);
|
65 | 64 | }
|
66 |
| - |
67 | 65 | // Unset!
|
68 | 66 | if (context.has(key)) {
|
69 | 67 | 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}`); |
73 | 106 | }
|
74 | 107 | }
|
| 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 | +} |
75 | 138 |
|
76 | 139 | function keyByNumber(context: Context, n: number) {
|
77 | 140 | for (const [i, key] of contextKeys(context)) {
|
|
0 commit comments