@friggframework/devtools 2.0.0--canary.490.feacde9.0 → 2.0.0--canary.497.a3f25f9.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.
Files changed (26) hide show
  1. package/frigg-cli/deploy-command/index.js +3 -9
  2. package/infrastructure/README.md +0 -28
  3. package/infrastructure/domains/database/migration-builder.js +13 -19
  4. package/infrastructure/domains/database/migration-builder.test.js +0 -57
  5. package/infrastructure/domains/integration/integration-builder.js +14 -19
  6. package/infrastructure/domains/integration/integration-builder.test.js +74 -0
  7. package/infrastructure/domains/networking/vpc-builder.js +18 -240
  8. package/infrastructure/domains/networking/vpc-builder.test.js +13 -711
  9. package/infrastructure/domains/networking/vpc-resolver.js +40 -221
  10. package/infrastructure/domains/networking/vpc-resolver.test.js +18 -318
  11. package/infrastructure/domains/security/kms-builder.js +6 -55
  12. package/infrastructure/domains/security/kms-builder.test.js +1 -19
  13. package/infrastructure/domains/shared/cloudformation-discovery.js +13 -310
  14. package/infrastructure/domains/shared/cloudformation-discovery.test.js +0 -395
  15. package/infrastructure/domains/shared/providers/aws-provider-adapter.js +6 -41
  16. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +0 -39
  17. package/infrastructure/domains/shared/resource-discovery.js +5 -17
  18. package/infrastructure/domains/shared/resource-discovery.test.js +0 -36
  19. package/infrastructure/domains/shared/utilities/base-definition-factory.js +17 -27
  20. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +0 -73
  21. package/infrastructure/infrastructure-composer.js +3 -11
  22. package/infrastructure/scripts/build-prisma-layer.js +81 -8
  23. package/infrastructure/scripts/build-prisma-layer.test.js +53 -1
  24. package/infrastructure/scripts/verify-prisma-layer.js +72 -0
  25. package/package.json +7 -7
  26. package/layers/prisma/.build-complete +0 -3
@@ -23,35 +23,13 @@ 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') {
30
- const externalVpcId = appDefinition.vpc?.external?.vpcId;
31
-
32
- // If hardcoded ID provided, use it
33
- if (externalVpcId) {
34
- return this.createExternalDecision(
35
- externalVpcId,
36
- 'User specified ownership=external with hardcoded vpcId'
37
- );
38
- }
39
-
40
- // No hardcoded ID - try discovery
41
- const discoveredVpcId = discovery.defaultVpcId;
42
-
43
- if (discoveredVpcId) {
44
- return this.createExternalDecision(
45
- discoveredVpcId,
46
- 'User specified ownership=external - using discovered VPC'
47
- );
48
- }
49
-
50
- // Discovery found nothing - error
51
- throw new Error(
52
- "ownership='external' for VPC requires either:\n" +
53
- " 1. Hardcoded external.vpcId, OR\n" +
54
- " 2. A VPC discovered via AWS discovery"
29
+ this.requireExternalIds(appDefinition.vpc?.external?.vpcId, 'vpcId');
30
+ return this.createExternalDecision(
31
+ appDefinition.vpc.external.vpcId,
32
+ 'User specified ownership=external for VPC'
55
33
  );
56
34
  }
57
35
 
@@ -65,35 +43,20 @@ class VpcResourceResolver extends BaseResourceResolver {
65
43
  }
66
44
 
67
45
  // Auto-decide
68
- const decision = this.resolveResourceOwnership(
46
+ return this.resolveResourceOwnership(
69
47
  'auto',
70
48
  'FriggVPC',
71
49
  'AWS::EC2::VPC',
72
50
  discovery
73
51
  );
74
-
75
- // CRITICAL: If auto-resolution wants to create a VPC but management mode is 'discover' (or undefined),
76
- // throw an error instead. Creating a VPC is expensive and should be explicit.
77
- if (decision.ownership === ResourceOwnership.STACK &&
78
- !decision.physicalId &&
79
- vpcManagement !== 'create-new' &&
80
- userIntent === 'auto') {
81
- throw new Error(
82
- 'VPC discovery failed: No VPC found. ' +
83
- 'Either set vpc.management to "create-new" or provide vpc.vpcId with vpc.management "use-existing".'
84
- );
85
- }
86
-
87
- return decision;
88
52
  }
89
53
 
90
54
  /**
91
55
  * Resolve Security Group ownership
92
56
  *
93
- * Logic:
94
- * - If FriggLambdaSecurityGroup exists in stack STACK (keep it)
95
- * - If default SG discovered from VPC EXTERNAL (use it)
96
- * - 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.
97
60
  *
98
61
  * @param {Object} appDefinition - App definition
99
62
  * @param {Object} discovery - Discovery result
@@ -102,95 +65,33 @@ class VpcResourceResolver extends BaseResourceResolver {
102
65
  resolveSecurityGroup(appDefinition, discovery) {
103
66
  const userIntent = appDefinition.vpc?.ownership?.securityGroup || 'auto';
104
67
 
105
- // Explicit external
68
+ // Explicit external - only use external SGs if user explicitly provides them
106
69
  if (userIntent === 'external') {
107
- const externalIds = appDefinition.vpc?.external?.securityGroupIds;
108
-
109
- // If hardcoded IDs provided, use those
110
- if (externalIds && externalIds.length > 0) {
111
- return this.createExternalDecision(
112
- externalIds,
113
- 'User specified ownership=external with hardcoded securityGroupIds'
114
- );
115
- }
116
-
117
- // No hardcoded IDs - try discovery
118
- const structured = discovery._structured || discovery;
119
-
120
- // When ownership='external', use ONLY the default SG, not the stack-managed lambda SG
121
- const lambdaSgId = structured.lambdaSecurityGroupId || discovery.lambdaSecurityGroupId;
122
- const defaultSgId = structured.defaultSecurityGroupId || discovery.defaultSecurityGroupId;
123
-
124
- // If we have a default SG AND it's different from the lambda SG, use the default
125
- if (defaultSgId && defaultSgId !== lambdaSgId) {
126
- return this.createExternalDecision(
127
- [defaultSgId],
128
- 'User specified ownership=external - using discovered default security group'
129
- );
130
- }
131
-
132
- // If only defaultSgId exists (no lambdaSgId), use it
133
- if (defaultSgId && !lambdaSgId) {
134
- return this.createExternalDecision(
135
- [defaultSgId],
136
- 'User specified ownership=external - using discovered default security group'
137
- );
138
- }
139
-
140
- // Discovery found nothing - error
141
- throw new Error(
142
- "ownership='external' for securityGroup requires either:\n" +
143
- " 1. Hardcoded external.securityGroupIds array, OR\n" +
144
- " 2. A default security group discovered via AWS discovery"
70
+ this.requireExternalIds(
71
+ appDefinition.vpc?.external?.securityGroupIds,
72
+ 'securityGroupIds'
145
73
  );
146
- }
147
-
148
- // Explicit stack - always create FriggLambdaSecurityGroup
149
- if (userIntent === 'stack') {
150
- const inStack = this.findInStack('FriggLambdaSecurityGroup', discovery);
151
- return this.createStackDecision(
152
- inStack?.physicalId,
153
- inStack
154
- ? 'Found FriggLambdaSecurityGroup in CloudFormation stack'
155
- : 'User specified ownership=stack - will create FriggLambdaSecurityGroup'
74
+ return this.createExternalDecision(
75
+ appDefinition.vpc.external.securityGroupIds,
76
+ 'User specified ownership=external for security group'
156
77
  );
157
78
  }
158
79
 
159
- // 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.
160
82
  const inStack = this.findInStack('FriggLambdaSecurityGroup', discovery);
161
83
 
162
84
  if (inStack) {
163
85
  return this.createStackDecision(
164
86
  inStack.physicalId,
165
- 'Found FriggLambdaSecurityGroup in CloudFormation stack - must keep in template'
166
- );
167
- }
168
-
169
- // Also check flat discovery for lambdaSecurityGroupId (from CloudFormation extraction)
170
- const structured = discovery._structured || discovery;
171
- const lambdaSgId = structured.lambdaSecurityGroupId || discovery.lambdaSecurityGroupId;
172
-
173
- if (lambdaSgId) {
174
- return this.createStackDecision(
175
- lambdaSgId,
176
- 'Found FriggLambdaSecurityGroup in CloudFormation stack - must keep in template'
177
- );
178
- }
179
-
180
- // Check for discovered default security group (from external VPC pattern)
181
- const defaultSgId = structured.defaultSecurityGroupId || discovery.defaultSecurityGroupId;
182
-
183
- if (defaultSgId) {
184
- return this.createExternalDecision(
185
- [defaultSgId],
186
- 'Found default security group via discovery - will reuse (matches canary behavior)'
87
+ 'Found FriggLambdaSecurityGroup in CloudFormation stack'
187
88
  );
188
89
  }
189
90
 
190
- // No SG found anywhere - create new FriggLambdaSecurityGroup
91
+ // Create new FriggLambdaSecurityGroup in stack
191
92
  return this.createStackDecision(
192
93
  null,
193
- 'No security group found - will create FriggLambdaSecurityGroup in stack'
94
+ 'No existing FriggLambdaSecurityGroup - will create in stack'
194
95
  );
195
96
  }
196
97
 
@@ -205,32 +106,13 @@ class VpcResourceResolver extends BaseResourceResolver {
205
106
 
206
107
  // Explicit external
207
108
  if (userIntent === 'external') {
208
- const externalSubnetIds = appDefinition.vpc?.external?.subnetIds;
209
-
210
- // If hardcoded IDs provided, use those
211
- if (externalSubnetIds && externalSubnetIds.length >= 2) {
212
- return this.createExternalDecision(
213
- externalSubnetIds,
214
- 'User specified ownership=external with hardcoded subnetIds'
215
- );
216
- }
217
-
218
- // No hardcoded IDs - try discovery
219
- const discoveredSubnet1 = discovery.privateSubnetId1;
220
- const discoveredSubnet2 = discovery.privateSubnetId2;
221
-
222
- if (discoveredSubnet1 && discoveredSubnet2) {
223
- return this.createExternalDecision(
224
- [discoveredSubnet1, discoveredSubnet2],
225
- 'User specified ownership=external - using discovered subnets'
226
- );
227
- }
228
-
229
- // Discovery found nothing - error
230
- throw new Error(
231
- "ownership='external' for subnets requires either:\n" +
232
- " 1. Hardcoded external.subnetIds array (minimum 2), OR\n" +
233
- " 2. At least 2 subnets discovered via AWS discovery"
109
+ this.requireExternalIds(
110
+ appDefinition.vpc?.external?.subnetIds,
111
+ 'subnetIds'
112
+ );
113
+ return this.createExternalDecision(
114
+ appDefinition.vpc.external.subnetIds,
115
+ 'User specified ownership=external for subnets'
234
116
  );
235
117
  }
236
118
 
@@ -318,31 +200,13 @@ class VpcResourceResolver extends BaseResourceResolver {
318
200
 
319
201
  // Explicit external
320
202
  if (userIntent === 'external') {
321
- const externalNatId = appDefinition.vpc?.external?.natGatewayId;
322
-
323
- // If hardcoded ID provided, use it
324
- if (externalNatId) {
325
- return this.createExternalDecision(
326
- externalNatId,
327
- 'User specified ownership=external with hardcoded natGatewayId'
328
- );
329
- }
330
-
331
- // No hardcoded ID - try discovery
332
- const discoveredNatId = discovery.natGatewayId;
333
-
334
- if (discoveredNatId) {
335
- return this.createExternalDecision(
336
- discoveredNatId,
337
- 'User specified ownership=external - using discovered NAT gateway'
338
- );
339
- }
340
-
341
- // Discovery found nothing - error
342
- throw new Error(
343
- "ownership='external' for NAT gateway requires either:\n" +
344
- " 1. Hardcoded external.natGatewayId, OR\n" +
345
- " 2. A NAT gateway discovered via AWS discovery"
203
+ this.requireExternalIds(
204
+ appDefinition.vpc?.external?.natGatewayId,
205
+ 'natGatewayId'
206
+ );
207
+ return this.createExternalDecision(
208
+ appDefinition.vpc.external.natGatewayId,
209
+ 'User specified ownership=external for NAT gateway'
346
210
  );
347
211
  }
348
212
 
@@ -389,16 +253,9 @@ class VpcResourceResolver extends BaseResourceResolver {
389
253
  const encryptionMethod = appDefinition.encryption?.fieldLevelEncryptionMethod;
390
254
  const needsKms = encryptionMethod === 'kms';
391
255
 
392
- // DynamoDB endpoint only needed if using DynamoDB (not MongoDB or PostgreSQL)
393
- // Currently framework only supports MongoDB (via Prisma) and PostgreSQL (via Aurora)
394
- // If not using DynamoDB, skip the endpoint (CloudFormation will delete if it exists)
395
- const usesDynamoDB = appDefinition.database?.dynamodb?.enable === true;
396
-
397
256
  const endpoints = {
398
257
  s3: this._resolveEndpoint('FriggS3VPCEndpoint', 's3', userIntent, appDefinition, discovery),
399
- dynamodb: usesDynamoDB
400
- ? this._resolveEndpoint('FriggDynamoDBVPCEndpoint', 'dynamodb', userIntent, appDefinition, discovery)
401
- : { ownership: null, reason: 'DynamoDB endpoint not needed (application uses MongoDB/PostgreSQL, not DynamoDB)' },
258
+ dynamodb: this._resolveEndpoint('FriggDynamoDBVPCEndpoint', 'dynamodb', userIntent, appDefinition, discovery),
402
259
  kms: needsKms
403
260
  ? this._resolveEndpoint('FriggKMSVPCEndpoint', 'kms', userIntent, appDefinition, discovery)
404
261
  : { ownership: null, reason: 'KMS endpoint not needed (encryption method is not KMS)' },
@@ -411,20 +268,9 @@ class VpcResourceResolver extends BaseResourceResolver {
411
268
 
412
269
  /**
413
270
  * Resolve individual VPC endpoint
414
- * Checks both old and new logical ID patterns for backwards compatibility
415
271
  * @private
416
272
  */
417
273
  _resolveEndpoint(logicalId, endpointType, userIntent, appDefinition, discovery) {
418
- // Map of old logical IDs (for backwards compatibility with stacks created before naming standardization)
419
- const oldLogicalIdMap = {
420
- 'FriggS3VPCEndpoint': 'VPCEndpointS3',
421
- 'FriggDynamoDBVPCEndpoint': 'VPCEndpointDynamoDB',
422
- 'FriggKMSVPCEndpoint': 'VPCEndpointKMS',
423
- 'FriggSecretsManagerVPCEndpoint': 'VPCEndpointSecretsManager',
424
- 'FriggSQSVPCEndpoint': 'VPCEndpointSQS'
425
- };
426
- const oldLogicalId = oldLogicalIdMap[logicalId];
427
-
428
274
  // Explicit external
429
275
  if (userIntent === 'external') {
430
276
  const externalId = appDefinition.vpc?.external?.vpcEndpointIds?.[endpointType];
@@ -438,49 +284,22 @@ class VpcResourceResolver extends BaseResourceResolver {
438
284
  return { ownership: null, reason: `External ${endpointType} endpoint ID not provided` };
439
285
  }
440
286
 
441
- // Explicit stack - check both old and new logical IDs
287
+ // Explicit stack
442
288
  if (userIntent === 'stack') {
443
- let inStack = this.findInStack(logicalId, discovery);
444
- if (!inStack && oldLogicalId) {
445
- inStack = this.findInStack(oldLogicalId, discovery);
446
- }
289
+ const inStack = this.findInStack(logicalId, discovery);
447
290
  return this.createStackDecision(
448
291
  inStack?.physicalId,
449
292
  `User specified ownership=stack for ${endpointType} endpoint`
450
293
  );
451
294
  }
452
295
 
453
- // Auto-decide - check if in stack first (try both old and new logical IDs)
454
- let inStack = this.isInStack(logicalId, discovery);
455
- let stackResource = inStack ? this.findInStack(logicalId, discovery) : null;
456
- let actualLogicalId = logicalId; // Track which ID we found
457
-
458
- // If not found with new ID, try old ID pattern
459
- if (!inStack && oldLogicalId) {
460
- inStack = this.isInStack(oldLogicalId, discovery);
461
- stackResource = inStack ? this.findInStack(oldLogicalId, discovery) : null;
462
- if (inStack) {
463
- actualLogicalId = oldLogicalId; // Found with old ID - use that for resolution
464
- }
465
- }
466
-
467
- const decision = this.resolveResourceOwnership(
296
+ // Auto-decide
297
+ return this.resolveResourceOwnership(
468
298
  'auto',
469
- actualLogicalId, // Use the actual ID we found (old or new)
299
+ logicalId,
470
300
  'AWS::EC2::VPCEndpoint',
471
301
  discovery
472
302
  );
473
-
474
- // Override reason with more detailed explanation
475
- if (decision.ownership === 'stack' && decision.physicalId) {
476
- decision.reason = `Found in CloudFormation stack (must keep in template to avoid deletion)`;
477
- } else if (decision.ownership === 'stack' && !decision.physicalId) {
478
- decision.reason = `No existing ${endpointType} endpoint found - will create in stack`;
479
- } else if (decision.ownership === 'external') {
480
- decision.reason = `Found external ${endpointType} endpoint via discovery`;
481
- }
482
-
483
- return decision;
484
303
  }
485
304
 
486
305
  /**