Skip to content

Commit 232e45b

Browse files
committed
fix(flags): flags commands do not forward context parameters to synth
1 parent a9d5b84 commit 232e45b

File tree

4 files changed

+161
-9
lines changed

4 files changed

+161
-9
lines changed

packages/aws-cdk/lib/cli/cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
498498
unstableFeatures: configuration.settings.get(['unstable']),
499499
});
500500
const flagsData = await toolkit.flags(cloudExecutable);
501-
const handler = new FlagCommandHandler(flagsData, ioHelper, args, toolkit);
501+
const handler = new FlagCommandHandler(flagsData, ioHelper, args, toolkit, configuration.context.all);
502502
return handler.processFlagsCommand();
503503

504504
case 'synthesize':

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,19 @@ export class FlagCommandHandler {
1414
private readonly ioHelper: IoHelper;
1515

1616
/** Main component that sets up all flag operation components */
17-
constructor(flagData: FeatureFlag[], ioHelper: IoHelper, options: FlagOperationsParams, toolkit: Toolkit) {
17+
constructor(
18+
flagData: FeatureFlag[],
19+
ioHelper: IoHelper,
20+
options: FlagOperationsParams,
21+
toolkit: Toolkit,
22+
cliContextValues: Record<string, any> = {},
23+
) {
1824
this.flags = flagData.filter(flag => !OBSOLETE_FLAGS.includes(flag.name));
1925
this.options = { ...options, concurrency: options.concurrency ?? 4 };
2026
this.ioHelper = ioHelper;
2127

2228
const validator = new FlagValidator(ioHelper);
23-
const flagOperations = new FlagOperations(this.flags, toolkit, ioHelper);
29+
const flagOperations = new FlagOperations(this.flags, toolkit, ioHelper, cliContextValues);
2430
const interactiveHandler = new InteractiveHandler(this.flags, flagOperations);
2531

2632
this.router = new FlagOperationRouter(validator, interactiveHandler, flagOperations);

packages/aws-cdk/lib/commands/flags/operations.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export class FlagOperations {
4848
private readonly flags: FeatureFlag[],
4949
private readonly toolkit: Toolkit,
5050
private readonly ioHelper: IoHelper,
51+
private readonly cliContextValues: Record<string, any> = {},
5152
) {
5253
this.app = '';
5354
this.baseContextValues = {};
@@ -159,11 +160,12 @@ export class FlagOperations {
159160
/** Initializes the safety check by reading context and synthesizing baseline templates */
160161
private async initializeSafetyCheck(): Promise<void> {
161162
const baseContext = new CdkAppMultiContext(process.cwd());
162-
this.baseContextValues = await baseContext.read();
163+
this.baseContextValues = { ...await baseContext.read(), ...this.cliContextValues };
163164

164165
this.baselineTempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cdk-baseline-'));
166+
const mergedContext = new MemoryContext(this.baseContextValues);
165167
const baseSource = await this.toolkit.fromCdkApp(this.app, {
166-
contextStore: baseContext,
168+
contextStore: mergedContext,
167169
outdir: this.baselineTempDir,
168170
});
169171

@@ -270,14 +272,14 @@ export class FlagOperations {
270272
/** Prototypes flag changes by synthesizing templates and showing diffs to the user */
271273
private async prototypeChanges(flagNames: string[], params: FlagOperationsParams): Promise<boolean> {
272274
const baseContext = new CdkAppMultiContext(process.cwd());
273-
const baseContextValues = await baseContext.read();
275+
const baseContextValues = { ...await baseContext.read(), ...this.cliContextValues };
274276
const memoryContext = new MemoryContext(baseContextValues);
275277

276278
const cdkJson = await JSON.parse(await fs.readFile(path.join(process.cwd(), 'cdk.json'), 'utf-8'));
277279
const app = cdkJson.app;
278280

279281
const source = await this.toolkit.fromCdkApp(app, {
280-
contextStore: baseContext,
282+
contextStore: memoryContext,
281283
outdir: fs.mkdtempSync(path.join(os.tmpdir(), 'cdk-original-')),
282284
});
283285

packages/aws-cdk/test/commands/flag-operations.test.ts

Lines changed: 146 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,144 @@ describe('interactive prompts lead to the correct function calls', () => {
11971197
});
11981198
});
11991199

1200+
describe('CLI context parameters', () => {
1201+
beforeEach(() => {
1202+
setupMockToolkitForPrototyping(mockToolkit);
1203+
jest.clearAllMocks();
1204+
});
1205+
1206+
test('CLI context values are merged with file context during prototyping', async () => {
1207+
const cdkJsonPath = await createCdkJsonFile({
1208+
'@aws-cdk/core:existingFlag': true,
1209+
});
1210+
1211+
setupMockToolkitForPrototyping(mockToolkit);
1212+
1213+
const requestResponseSpy = jest.spyOn(ioHelper, 'requestResponse');
1214+
requestResponseSpy.mockResolvedValue(false);
1215+
1216+
const cliContextValues = {
1217+
foo: 'bar',
1218+
myContextParam: 'myValue',
1219+
};
1220+
1221+
const options: FlagsOptions = {
1222+
FLAGNAME: ['@aws-cdk/core:testFlag'],
1223+
set: true,
1224+
value: 'true',
1225+
};
1226+
1227+
const flagOperations = new FlagCommandHandler(mockFlagsData, ioHelper, options, mockToolkit, cliContextValues);
1228+
await flagOperations.processFlagsCommand();
1229+
1230+
// Verify that fromCdkApp was called with a context store
1231+
expect(mockToolkit.fromCdkApp).toHaveBeenCalledTimes(2);
1232+
1233+
// Get the first call's context store
1234+
const firstCallArgs = mockToolkit.fromCdkApp.mock.calls[0];
1235+
const contextStore = firstCallArgs[1]?.contextStore;
1236+
1237+
// Verify the context store is a MemoryContext that contains both file and CLI context
1238+
expect(contextStore).toBeDefined();
1239+
1240+
await cleanupCdkJsonFile(cdkJsonPath);
1241+
requestResponseSpy.mockRestore();
1242+
});
1243+
1244+
test('CLI context values are passed to synthesis during safe flag checking', async () => {
1245+
const cdkJsonPath = await createCdkJsonFile({});
1246+
1247+
mockToolkit.diff.mockResolvedValue({
1248+
TestStack: { differenceCount: 0 } as any,
1249+
});
1250+
1251+
const requestResponseSpy = jest.spyOn(ioHelper, 'requestResponse');
1252+
requestResponseSpy.mockResolvedValue(false);
1253+
1254+
const cliContextValues = {
1255+
myCliParam: 'cliValue',
1256+
};
1257+
1258+
const options: FlagsOptions = {
1259+
safe: true,
1260+
concurrency: 4,
1261+
};
1262+
1263+
const flagOperations = new FlagCommandHandler(mockFlagsData, ioHelper, options, mockToolkit, cliContextValues);
1264+
await flagOperations.processFlagsCommand();
1265+
1266+
// Verify that fromCdkApp was called during safe flag checking
1267+
expect(mockToolkit.fromCdkApp).toHaveBeenCalled();
1268+
1269+
await cleanupCdkJsonFile(cdkJsonPath);
1270+
requestResponseSpy.mockRestore();
1271+
});
1272+
1273+
test('works correctly when no CLI context is provided', async () => {
1274+
const cdkJsonPath = await createCdkJsonFile({
1275+
'@aws-cdk/core:existingFlag': false,
1276+
});
1277+
1278+
setupMockToolkitForPrototyping(mockToolkit);
1279+
1280+
const requestResponseSpy = jest.spyOn(ioHelper, 'requestResponse');
1281+
requestResponseSpy.mockResolvedValue(false);
1282+
1283+
const options: FlagsOptions = {
1284+
FLAGNAME: ['@aws-cdk/core:testFlag'],
1285+
set: true,
1286+
value: 'true',
1287+
};
1288+
1289+
// Not passing cliContextValues (defaults to empty object)
1290+
const flagOperations = new FlagCommandHandler(mockFlagsData, ioHelper, options, mockToolkit);
1291+
await flagOperations.processFlagsCommand();
1292+
1293+
expect(mockToolkit.fromCdkApp).toHaveBeenCalledTimes(2);
1294+
expect(mockToolkit.synth).toHaveBeenCalledTimes(2);
1295+
1296+
await cleanupCdkJsonFile(cdkJsonPath);
1297+
requestResponseSpy.mockRestore();
1298+
});
1299+
1300+
test('FlagCommandHandler constructor accepts CLI context values', () => {
1301+
const cliContextValues = {
1302+
myParam: 'myValue',
1303+
foo: 'bar',
1304+
};
1305+
1306+
const options: FlagsOptions = {
1307+
all: true,
1308+
};
1309+
1310+
// Verify that constructor accepts CLI context values parameter
1311+
const handler = new FlagCommandHandler(mockFlagsData, ioHelper, options, mockToolkit, cliContextValues);
1312+
expect(handler).toBeDefined();
1313+
});
1314+
1315+
test('FlagCommandHandler passes CLI context to FlagOperations', async () => {
1316+
const cdkJsonPath = await createCdkJsonFile({});
1317+
1318+
setupMockToolkitForPrototyping(mockToolkit);
1319+
1320+
const cliContextValues = {
1321+
cliParam: 'cliValue',
1322+
};
1323+
1324+
const options: FlagsOptions = {
1325+
all: true,
1326+
};
1327+
1328+
const handler = new FlagCommandHandler(mockFlagsData, ioHelper, options, mockToolkit, cliContextValues);
1329+
await handler.processFlagsCommand();
1330+
1331+
// Just verify it doesn't throw and completes successfully
1332+
expect(mockToolkit.fromCdkApp).not.toHaveBeenCalled(); // Display mode doesn't call synthesis
1333+
1334+
await cleanupCdkJsonFile(cdkJsonPath);
1335+
});
1336+
});
1337+
12001338
describe('setSafeFlags', () => {
12011339
beforeEach(() => {
12021340
setupMockToolkitForPrototyping(mockToolkit);
@@ -1390,7 +1528,13 @@ async function displayFlags(params: FlagOperationsParams): Promise<void> {
13901528
await f.displayFlags(params);
13911529
}
13921530

1393-
async function handleFlags(flagData: FeatureFlag[], _ioHelper: IoHelper, options: FlagsOptions, toolkit: Toolkit) {
1394-
const f = new FlagCommandHandler(flagData, _ioHelper, options, toolkit);
1531+
async function handleFlags(
1532+
flagData: FeatureFlag[],
1533+
_ioHelper: IoHelper,
1534+
options: FlagsOptions,
1535+
toolkit: Toolkit,
1536+
cliContextValues: Record<string, any> = {},
1537+
) {
1538+
const f = new FlagCommandHandler(flagData, _ioHelper, options, toolkit, cliContextValues);
13951539
await f.processFlagsCommand();
13961540
}

0 commit comments

Comments
 (0)