@friggframework/devtools 2.0.0--canary.490.de9ed00.0 → 2.0.0--canary.493.f8d621f.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -149,7 +149,7 @@ class VpcBuilder extends InfrastructureBuilder {
149
149
  physicalId = flatDiscovery.defaultVpcId;
150
150
  } else if (logicalId === 'FriggLambdaSecurityGroup') {
151
151
  resourceType = 'AWS::EC2::SecurityGroup';
152
- physicalId = flatDiscovery.lambdaSecurityGroupId || flatDiscovery.defaultSecurityGroupId || flatDiscovery.securityGroupId;
152
+ physicalId = flatDiscovery.defaultSecurityGroupId || flatDiscovery.securityGroupId;
153
153
  } else if (logicalId === 'FriggPrivateSubnet1') {
154
154
  resourceType = 'AWS::EC2::Subnet';
155
155
  physicalId = flatDiscovery.privateSubnetId1;
@@ -159,9 +159,6 @@ class VpcBuilder extends InfrastructureBuilder {
159
159
  } else if (logicalId === 'FriggNATGateway') {
160
160
  resourceType = 'AWS::EC2::NatGateway';
161
161
  physicalId = flatDiscovery.existingNatGatewayId;
162
- } else if (logicalId === 'FriggLambdaRouteTable') {
163
- resourceType = 'AWS::EC2::RouteTable';
164
- physicalId = flatDiscovery.routeTableId;
165
162
  } else if (logicalId === 'FriggS3VPCEndpoint') {
166
163
  resourceType = 'AWS::EC2::VPCEndpoint';
167
164
  physicalId = flatDiscovery.s3VpcEndpointId;
@@ -187,11 +184,6 @@ class VpcBuilder extends InfrastructureBuilder {
187
184
  });
188
185
  }
189
186
  });
190
-
191
- // Also check for external resources extracted via CloudFormation queries
192
- // (e.g., VPC ID from security group query, subnets from route table associations)
193
- // These are NOT in the stack but were discovered through stack resources
194
- this._addExternalResourcesFromCloudFormationQueries(flatDiscovery, discovery, existingLogicalIds);
195
187
  } else {
196
188
  // Resources discovered from AWS API (not CloudFormation)
197
189
  // These go into external array
@@ -298,58 +290,6 @@ class VpcBuilder extends InfrastructureBuilder {
298
290
  return discovery;
299
291
  }
300
292
 
301
- /**
302
- * Add external resources that were discovered via CloudFormation queries
303
- * (e.g., VPC ID extracted from security group, subnets from route table associations)
304
- *
305
- * @private
306
- */
307
- _addExternalResourcesFromCloudFormationQueries(flatDiscovery, discovery, existingLogicalIds) {
308
- // VPC ID extracted from SG or route table (NOT a stack resource)
309
- if (flatDiscovery.defaultVpcId &&
310
- typeof flatDiscovery.defaultVpcId === 'string' &&
311
- !existingLogicalIds.includes('FriggVPC')) {
312
- discovery.external.push({
313
- physicalId: flatDiscovery.defaultVpcId,
314
- resourceType: 'AWS::EC2::VPC',
315
- source: 'cloudformation-query'
316
- });
317
- }
318
-
319
- // Subnets extracted from route table associations (NOT stack resources)
320
- if (flatDiscovery.privateSubnetId1 &&
321
- typeof flatDiscovery.privateSubnetId1 === 'string' &&
322
- !existingLogicalIds.includes('FriggPrivateSubnet1')) {
323
- discovery.external.push({
324
- physicalId: flatDiscovery.privateSubnetId1,
325
- resourceType: 'AWS::EC2::Subnet',
326
- source: 'cloudformation-query'
327
- });
328
- }
329
-
330
- if (flatDiscovery.privateSubnetId2 &&
331
- typeof flatDiscovery.privateSubnetId2 === 'string' &&
332
- !existingLogicalIds.includes('FriggPrivateSubnet2')) {
333
- discovery.external.push({
334
- physicalId: flatDiscovery.privateSubnetId2,
335
- resourceType: 'AWS::EC2::Subnet',
336
- source: 'cloudformation-query'
337
- });
338
- }
339
-
340
- // NAT Gateway extracted from route table routes
341
- if (flatDiscovery.existingNatGatewayId &&
342
- typeof flatDiscovery.existingNatGatewayId === 'string' &&
343
- !existingLogicalIds.includes('FriggNATGateway') &&
344
- !existingLogicalIds.includes('FriggNatGateway')) {
345
- discovery.external.push({
346
- physicalId: flatDiscovery.existingNatGatewayId,
347
- resourceType: 'AWS::EC2::NatGateway',
348
- source: 'cloudformation-query'
349
- });
350
- }
351
- }
352
-
353
293
  /**
354
294
  * Translate legacy configuration (management modes) to new ownership-based configuration
355
295
  * Provides backwards compatibility for existing app definitions
@@ -932,8 +872,6 @@ class VpcBuilder extends InfrastructureBuilder {
932
872
 
933
873
  if (endpointsInStack.length > 0) {
934
874
  console.log(` ✓ VPC Endpoints in stack: ${endpointsInStack.join(', ')}`);
935
- // CRITICAL: Must add stack-managed endpoints back to template or CloudFormation will DELETE them!
936
- this._addStackManagedEndpointsToTemplate(decisions, result);
937
875
  }
938
876
 
939
877
  if (externalEndpoints.length > 0) {
@@ -1114,101 +1052,6 @@ class VpcBuilder extends InfrastructureBuilder {
1114
1052
  return healingReport;
1115
1053
  }
1116
1054
 
1117
- /**
1118
- * Add stack-managed VPC endpoints back to template
1119
- *
1120
- * CRITICAL: CloudFormation will DELETE resources that exist in the previous template
1121
- * but are missing from the new template. We must re-add discovered stack-managed
1122
- * endpoints to prevent CloudFormation from deleting them.
1123
- *
1124
- * @private
1125
- */
1126
- _addStackManagedEndpointsToTemplate(decisions, result) {
1127
- const vpcId = result.vpcId;
1128
- const logicalIdMap = {
1129
- s3: 'FriggS3VPCEndpoint',
1130
- dynamodb: 'FriggDynamoDBVPCEndpoint',
1131
- kms: 'FriggKMSVPCEndpoint',
1132
- secretsManager: 'FriggSecretsManagerVPCEndpoint',
1133
- sqs: 'FriggSQSVPCEndpoint'
1134
- };
1135
-
1136
- Object.entries(decisions).forEach(([type, decision]) => {
1137
- if (decision.ownership === ResourceOwnership.STACK && decision.physicalId) {
1138
- const logicalId = logicalIdMap[type];
1139
-
1140
- // Determine endpoint type and properties based on service
1141
- if (type === 's3') {
1142
- result.resources[logicalId] = {
1143
- Type: 'AWS::EC2::VPCEndpoint',
1144
- Properties: {
1145
- VpcId: vpcId,
1146
- ServiceName: 'com.amazonaws.${self:provider.region}.s3',
1147
- VpcEndpointType: 'Gateway',
1148
- RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }]
1149
- }
1150
- };
1151
- } else if (type === 'dynamodb') {
1152
- result.resources[logicalId] = {
1153
- Type: 'AWS::EC2::VPCEndpoint',
1154
- Properties: {
1155
- VpcId: vpcId,
1156
- ServiceName: 'com.amazonaws.${self:provider.region}.dynamodb',
1157
- VpcEndpointType: 'Gateway',
1158
- RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }]
1159
- }
1160
- };
1161
- } else {
1162
- // Interface endpoints (KMS, Secrets Manager, SQS)
1163
- const serviceMap = {
1164
- kms: 'kms',
1165
- secretsManager: 'secretsmanager',
1166
- sqs: 'sqs'
1167
- };
1168
-
1169
- result.resources[logicalId] = {
1170
- Type: 'AWS::EC2::VPCEndpoint',
1171
- Properties: {
1172
- VpcId: vpcId,
1173
- ServiceName: `com.amazonaws.\${self:provider.region}.${serviceMap[type]}`,
1174
- VpcEndpointType: 'Interface',
1175
- SubnetIds: result.vpcConfig.subnetIds,
1176
- SecurityGroupIds: [{ Ref: 'FriggVPCEndpointSecurityGroup' }],
1177
- PrivateDnsEnabled: true
1178
- }
1179
- };
1180
- }
1181
- }
1182
- });
1183
-
1184
- // If any interface endpoints exist, ensure security group is in template
1185
- const hasInterfaceEndpoints = ['kms', 'secretsManager', 'sqs'].some(
1186
- type => decisions[type]?.ownership === ResourceOwnership.STACK && decisions[type]?.physicalId
1187
- );
1188
-
1189
- if (hasInterfaceEndpoints && !result.resources.FriggVPCEndpointSecurityGroup) {
1190
- result.resources.FriggVPCEndpointSecurityGroup = {
1191
- Type: 'AWS::EC2::SecurityGroup',
1192
- Properties: {
1193
- GroupDescription: 'Security group for VPC Endpoints',
1194
- VpcId: vpcId,
1195
- SecurityGroupIngress: [
1196
- {
1197
- IpProtocol: 'tcp',
1198
- FromPort: 443,
1199
- ToPort: 443,
1200
- SourceSecurityGroupId: { Ref: 'FriggLambdaSecurityGroup' }
1201
- }
1202
- ],
1203
- Tags: [
1204
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc-endpoint-sg' },
1205
- { Key: 'ManagedBy', Value: 'Frigg' }
1206
- ]
1207
- }
1208
- };
1209
- }
1210
- }
1211
-
1212
1055
  /**
1213
1056
  * Build new VPC from scratch
1214
1057
  */
@@ -1818,7 +1661,7 @@ class VpcBuilder extends InfrastructureBuilder {
1818
1661
  // Stack-managed resources should be reused, not recreated
1819
1662
  const stackManagedEndpoints = {
1820
1663
  s3: discoveredResources.s3VpcEndpointId && typeof discoveredResources.s3VpcEndpointId === 'string',
1821
- dynamodb: discoveredResources.dynamodbVpcEndpointId && typeof discoveredResources.dynamodbVpcEndpointId === 'string',
1664
+ dynamodb: discoveredResources.dynamoDbVpcEndpointId && typeof discoveredResources.dynamoDbVpcEndpointId === 'string',
1822
1665
  kms: discoveredResources.kmsVpcEndpointId && typeof discoveredResources.kmsVpcEndpointId === 'string',
1823
1666
  secretsManager: discoveredResources.secretsManagerVpcEndpointId && typeof discoveredResources.secretsManagerVpcEndpointId === 'string',
1824
1667
  sqs: discoveredResources.sqsVpcEndpointId && typeof discoveredResources.sqsVpcEndpointId === 'string',
@@ -1258,145 +1258,5 @@ describe('VpcBuilder', () => {
1258
1258
  expect(result.outputs.PrivateSubnet2Id).toBeDefined();
1259
1259
  });
1260
1260
  });
1261
-
1262
- describe('convertFlatDiscoveryToStructured - CloudFormation query results', () => {
1263
- it('should add VPC from CloudFormation query to external array', () => {
1264
- const flatDiscovery = {
1265
- fromCloudFormationStack: true,
1266
- stackName: 'test-stack',
1267
- existingLogicalIds: ['FriggLambdaRouteTable', 'FriggLambdaSecurityGroup'],
1268
- // VPC ID was extracted from security group query (NOT a stack resource)
1269
- defaultVpcId: 'vpc-extracted-from-sg',
1270
- lambdaSecurityGroupId: 'sg-123',
1271
- routeTableId: 'rtb-123'
1272
- };
1273
-
1274
- const result = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
1275
-
1276
- // VPC should be in external array (discovered via query, not in stack)
1277
- const vpcExternal = result.external.find(r => r.resourceType === 'AWS::EC2::VPC');
1278
- expect(vpcExternal).toBeDefined();
1279
- expect(vpcExternal.physicalId).toBe('vpc-extracted-from-sg');
1280
- expect(vpcExternal.source).toBe('cloudformation-query');
1281
-
1282
- // Security group SHOULD be in stackManaged (is in stack)
1283
- const sgStack = result.stackManaged.find(r => r.logicalId === 'FriggLambdaSecurityGroup');
1284
- expect(sgStack).toBeDefined();
1285
- expect(sgStack.physicalId).toBe('sg-123');
1286
- });
1287
-
1288
- it('should add subnets from route table associations to external array', () => {
1289
- const flatDiscovery = {
1290
- fromCloudFormationStack: true,
1291
- stackName: 'test-stack',
1292
- existingLogicalIds: ['FriggLambdaRouteTable'],
1293
- routeTableId: 'rtb-123',
1294
- // Subnets extracted from route table associations (NOT stack resources)
1295
- privateSubnetId1: 'subnet-1',
1296
- privateSubnetId2: 'subnet-2'
1297
- };
1298
-
1299
- const result = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
1300
-
1301
- // Subnets should be in external array
1302
- const subnet1 = result.external.find(r => r.physicalId === 'subnet-1');
1303
- const subnet2 = result.external.find(r => r.physicalId === 'subnet-2');
1304
-
1305
- expect(subnet1).toBeDefined();
1306
- expect(subnet1.resourceType).toBe('AWS::EC2::Subnet');
1307
- expect(subnet1.source).toBe('cloudformation-query');
1308
-
1309
- expect(subnet2).toBeDefined();
1310
- expect(subnet2.resourceType).toBe('AWS::EC2::Subnet');
1311
- expect(subnet2.source).toBe('cloudformation-query');
1312
- });
1313
-
1314
- it('should add NAT Gateway from route table queries to external array', () => {
1315
- const flatDiscovery = {
1316
- fromCloudFormationStack: true,
1317
- stackName: 'test-stack',
1318
- existingLogicalIds: ['FriggLambdaRouteTable', 'FriggPrivateRoute'],
1319
- routeTableId: 'rtb-123',
1320
- // NAT Gateway extracted from route table routes (NOT a stack resource)
1321
- existingNatGatewayId: 'nat-extracted'
1322
- };
1323
-
1324
- const result = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
1325
-
1326
- // NAT should be in external array
1327
- const natExternal = result.external.find(r => r.resourceType === 'AWS::EC2::NatGateway');
1328
- expect(natExternal).toBeDefined();
1329
- expect(natExternal.physicalId).toBe('nat-extracted');
1330
- expect(natExternal.source).toBe('cloudformation-query');
1331
- });
1332
-
1333
- it('should NOT add resources to external if they are in stack', () => {
1334
- const flatDiscovery = {
1335
- fromCloudFormationStack: true,
1336
- stackName: 'test-stack',
1337
- existingLogicalIds: ['FriggVPC', 'FriggPrivateSubnet1'],
1338
- // These ARE in the stack
1339
- defaultVpcId: 'vpc-in-stack',
1340
- privateSubnetId1: 'subnet-in-stack'
1341
- };
1342
-
1343
- const result = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
1344
-
1345
- // Should be in stackManaged, NOT external
1346
- expect(result.stackManaged.some(r => r.logicalId === 'FriggVPC')).toBe(true);
1347
- expect(result.stackManaged.some(r => r.logicalId === 'FriggPrivateSubnet1')).toBe(true);
1348
-
1349
- // Should NOT be in external
1350
- expect(result.external.some(r => r.physicalId === 'vpc-in-stack')).toBe(false);
1351
- expect(result.external.some(r => r.physicalId === 'subnet-in-stack')).toBe(false);
1352
- });
1353
-
1354
- it('should handle Frontify pattern: stack resources + queried external references', () => {
1355
- const flatDiscovery = {
1356
- fromCloudFormationStack: true,
1357
- stackName: 'create-frigg-app-production',
1358
- existingLogicalIds: [
1359
- 'FriggLambdaSecurityGroup',
1360
- 'FriggLambdaRouteTable',
1361
- 'FriggPrivateRoute',
1362
- 'FriggPrivateSubnet1RouteTableAssociation',
1363
- 'FriggPrivateSubnet2RouteTableAssociation',
1364
- 'FriggS3VPCEndpoint',
1365
- 'FriggDynamoDBVPCEndpoint',
1366
- 'FriggKMSVPCEndpoint'
1367
- ],
1368
- // Stack resources
1369
- lambdaSecurityGroupId: 'sg-01002240c6a446202',
1370
- routeTableId: 'rtb-08af43bbf0775602d',
1371
- s3VpcEndpointId: 'vpce-s3',
1372
- // External resources (discovered via queries)
1373
- defaultVpcId: 'vpc-0cd17c0e06cb28b28',
1374
- privateSubnetId1: 'subnet-034f6562dbbc16348',
1375
- privateSubnetId2: 'subnet-0b8be2b82aeb5cdec',
1376
- existingNatGatewayId: 'nat-022660c36a47e2d79'
1377
- };
1378
-
1379
- const result = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
1380
-
1381
- // Stack resources should be in stackManaged
1382
- expect(result.stackManaged).toEqual(
1383
- expect.arrayContaining([
1384
- expect.objectContaining({ logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-01002240c6a446202' }),
1385
- expect.objectContaining({ logicalId: 'FriggLambdaRouteTable', physicalId: 'rtb-08af43bbf0775602d' }),
1386
- expect.objectContaining({ logicalId: 'FriggS3VPCEndpoint' })
1387
- ])
1388
- );
1389
-
1390
- // External resources should be in external array
1391
- expect(result.external).toEqual(
1392
- expect.arrayContaining([
1393
- expect.objectContaining({ physicalId: 'vpc-0cd17c0e06cb28b28', resourceType: 'AWS::EC2::VPC', source: 'cloudformation-query' }),
1394
- expect.objectContaining({ physicalId: 'subnet-034f6562dbbc16348', resourceType: 'AWS::EC2::Subnet', source: 'cloudformation-query' }),
1395
- expect.objectContaining({ physicalId: 'subnet-0b8be2b82aeb5cdec', resourceType: 'AWS::EC2::Subnet', source: 'cloudformation-query' }),
1396
- expect.objectContaining({ physicalId: 'nat-022660c36a47e2d79', resourceType: 'AWS::EC2::NatGateway', source: 'cloudformation-query' })
1397
- ])
1398
- );
1399
- });
1400
- });
1401
1261
  });
1402
1262
 
@@ -23,7 +23,6 @@ class VpcResourceResolver extends BaseResourceResolver {
23
23
  */
24
24
  resolveVpc(appDefinition, discovery) {
25
25
  const userIntent = appDefinition.vpc?.ownership?.vpc || 'auto';
26
- const vpcManagement = appDefinition.vpc?.management; // Legacy config
27
26
 
28
27
  // Explicit external
29
28
  if (userIntent === 'external') {
@@ -44,35 +43,20 @@ class VpcResourceResolver extends BaseResourceResolver {
44
43
  }
45
44
 
46
45
  // Auto-decide
47
- const decision = this.resolveResourceOwnership(
46
+ return this.resolveResourceOwnership(
48
47
  'auto',
49
48
  'FriggVPC',
50
49
  'AWS::EC2::VPC',
51
50
  discovery
52
51
  );
53
-
54
- // CRITICAL: If auto-resolution wants to create a VPC but management mode is 'discover' (or undefined),
55
- // throw an error instead. Creating a VPC is expensive and should be explicit.
56
- if (decision.ownership === ResourceOwnership.STACK &&
57
- !decision.physicalId &&
58
- vpcManagement !== 'create-new' &&
59
- userIntent === 'auto') {
60
- throw new Error(
61
- 'VPC discovery failed: No VPC found. ' +
62
- 'Either set vpc.management to "create-new" or provide vpc.vpcId with vpc.management "use-existing".'
63
- );
64
- }
65
-
66
- return decision;
67
52
  }
68
53
 
69
54
  /**
70
55
  * Resolve Security Group ownership
71
56
  *
72
- * Logic:
73
- * - If FriggLambdaSecurityGroup exists in stack STACK (keep it)
74
- * - If default SG discovered from VPC EXTERNAL (use it)
75
- * - Otherwise → STACK (create FriggLambdaSecurityGroup)
57
+ * Special logic: We ALWAYS create our own FriggLambdaSecurityGroup with specific
58
+ * rules unless the user explicitly provides external SG IDs. The discovered
59
+ * defaultSecurityGroupId is the VPC's default SG, but we need our own Lambda SG.
76
60
  *
77
61
  * @param {Object} appDefinition - App definition
78
62
  * @param {Object} discovery - Discovery result
@@ -81,7 +65,7 @@ class VpcResourceResolver extends BaseResourceResolver {
81
65
  resolveSecurityGroup(appDefinition, discovery) {
82
66
  const userIntent = appDefinition.vpc?.ownership?.securityGroup || 'auto';
83
67
 
84
- // Explicit external - use provided SG IDs
68
+ // Explicit external - only use external SGs if user explicitly provides them
85
69
  if (userIntent === 'external') {
86
70
  this.requireExternalIds(
87
71
  appDefinition.vpc?.external?.securityGroupIds,
@@ -93,52 +77,21 @@ class VpcResourceResolver extends BaseResourceResolver {
93
77
  );
94
78
  }
95
79
 
96
- // Explicit stack - always create FriggLambdaSecurityGroup
97
- if (userIntent === 'stack') {
98
- const inStack = this.findInStack('FriggLambdaSecurityGroup', discovery);
99
- return this.createStackDecision(
100
- inStack?.physicalId,
101
- inStack
102
- ? 'Found FriggLambdaSecurityGroup in CloudFormation stack'
103
- : 'User specified ownership=stack - will create FriggLambdaSecurityGroup'
104
- );
105
- }
106
-
107
- // Auto mode: Check stack first, then check for discovered default SG
80
+ // For stack or auto: check if FriggLambdaSecurityGroup exists in stack
81
+ // If it does, reuse it. If not, create it. Never use discovered default SG.
108
82
  const inStack = this.findInStack('FriggLambdaSecurityGroup', discovery);
109
83
 
110
84
  if (inStack) {
111
85
  return this.createStackDecision(
112
86
  inStack.physicalId,
113
- 'Found FriggLambdaSecurityGroup in CloudFormation stack - must keep in template'
114
- );
115
- }
116
-
117
- // Also check flat discovery for lambdaSecurityGroupId (from CloudFormation extraction)
118
- const structured = discovery._structured || discovery;
119
- const lambdaSgId = structured.lambdaSecurityGroupId || discovery.lambdaSecurityGroupId;
120
-
121
- if (lambdaSgId) {
122
- return this.createStackDecision(
123
- lambdaSgId,
124
- 'Found FriggLambdaSecurityGroup in CloudFormation stack - must keep in template'
125
- );
126
- }
127
-
128
- // Check for discovered default security group (from external VPC pattern)
129
- const defaultSgId = structured.defaultSecurityGroupId || discovery.defaultSecurityGroupId;
130
-
131
- if (defaultSgId) {
132
- return this.createExternalDecision(
133
- [defaultSgId],
134
- 'Found default security group via discovery - will reuse (matches canary behavior)'
87
+ 'Found FriggLambdaSecurityGroup in CloudFormation stack'
135
88
  );
136
89
  }
137
90
 
138
- // No SG found anywhere - create new FriggLambdaSecurityGroup
91
+ // Create new FriggLambdaSecurityGroup in stack
139
92
  return this.createStackDecision(
140
93
  null,
141
- 'No security group found - will create FriggLambdaSecurityGroup in stack'
94
+ 'No existing FriggLambdaSecurityGroup - will create in stack'
142
95
  );
143
96
  }
144
97
 
@@ -109,46 +109,6 @@ describe('VpcResourceResolver', () => {
109
109
  expect(decision.physicalId).toBeUndefined();
110
110
  expect(decision.reason).toContain('No existing resource found');
111
111
  });
112
-
113
- it('should throw error when auto mode finds no VPC and management is not create-new', () => {
114
- const appDefinition = {
115
- vpc: {
116
- enable: true,
117
- // No management specified - defaults to discover
118
- // No ownership specified - defaults to auto
119
- }
120
- };
121
- const discovery = {
122
- stackManaged: [],
123
- external: [],
124
- fromCloudFormation: false
125
- };
126
-
127
- // Should throw error instead of trying to create VPC
128
- expect(() => resolver.resolveVpc(appDefinition, discovery)).toThrow(
129
- 'VPC discovery failed: No VPC found'
130
- );
131
- });
132
-
133
- it('should allow creating VPC when management is create-new', () => {
134
- const appDefinition = {
135
- vpc: {
136
- enable: true,
137
- management: 'create-new'
138
- }
139
- };
140
- const discovery = {
141
- stackManaged: [],
142
- external: [],
143
- fromCloudFormation: false
144
- };
145
-
146
- // Should NOT throw - create-new explicitly allows VPC creation
147
- const decision = resolver.resolveVpc(appDefinition, discovery);
148
-
149
- expect(decision.ownership).toBe(ResourceOwnership.STACK);
150
- expect(decision.physicalId).toBeNull();
151
- });
152
112
  });
153
113
 
154
114
  describe('resolveSecurityGroup', () => {