@friggframework/devtools 2.0.0-next.53 → 2.0.0-next.55

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 (32) hide show
  1. package/frigg-cli/README.md +13 -14
  2. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +267 -166
  3. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +45 -14
  4. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +44 -3
  5. package/frigg-cli/db-setup-command/index.js +75 -22
  6. package/frigg-cli/deploy-command/index.js +6 -3
  7. package/frigg-cli/utils/database-validator.js +18 -5
  8. package/frigg-cli/utils/error-messages.js +84 -12
  9. package/infrastructure/README.md +28 -0
  10. package/infrastructure/domains/database/migration-builder.js +26 -20
  11. package/infrastructure/domains/database/migration-builder.test.js +27 -0
  12. package/infrastructure/domains/integration/integration-builder.js +17 -13
  13. package/infrastructure/domains/integration/integration-builder.test.js +23 -0
  14. package/infrastructure/domains/networking/vpc-builder.js +240 -18
  15. package/infrastructure/domains/networking/vpc-builder.test.js +711 -13
  16. package/infrastructure/domains/networking/vpc-resolver.js +221 -40
  17. package/infrastructure/domains/networking/vpc-resolver.test.js +318 -18
  18. package/infrastructure/domains/security/kms-builder.js +55 -6
  19. package/infrastructure/domains/security/kms-builder.test.js +19 -1
  20. package/infrastructure/domains/shared/cloudformation-discovery.js +310 -13
  21. package/infrastructure/domains/shared/cloudformation-discovery.test.js +395 -0
  22. package/infrastructure/domains/shared/providers/aws-provider-adapter.js +41 -6
  23. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +39 -0
  24. package/infrastructure/domains/shared/resource-discovery.js +17 -5
  25. package/infrastructure/domains/shared/resource-discovery.test.js +36 -0
  26. package/infrastructure/domains/shared/utilities/base-definition-factory.js +30 -20
  27. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +43 -0
  28. package/infrastructure/infrastructure-composer.js +11 -3
  29. package/infrastructure/scripts/build-prisma-layer.js +153 -78
  30. package/infrastructure/scripts/build-prisma-layer.test.js +27 -11
  31. package/layers/prisma/.build-complete +3 -0
  32. package/package.json +7 -7
@@ -257,6 +257,34 @@ aws lambda get-function-configuration \
257
257
  --query 'Layers[*].Arn'
258
258
  ```
259
259
 
260
+ **Disabling Prisma Layer (Bundle with Functions):**
261
+
262
+ By default, Prisma ships via a Lambda Layer. You can disable the layer and bundle Prisma directly with each Lambda:
263
+
264
+ ```javascript
265
+ const appDefinition = {
266
+ name: 'my-app',
267
+ usePrismaLambdaLayer: false, // Bundle Prisma with each function
268
+ integrations: [{ Definition: { name: 'asana' } }],
269
+ };
270
+ ```
271
+
272
+ **When to disable the Prisma Layer:**
273
+
274
+ - ✅ Your CI/CD IAM user lacks `lambda:PublishLayerVersion`
275
+ - ✅ Deploying to an AWS region/account with Lambda layer restrictions
276
+ - ✅ Troubleshooting Prisma client loading issues
277
+ - ✅ You prefer a simpler deployment artifact (no shared layer management)
278
+
279
+ **Trade-offs:**
280
+
281
+ | Mode | Function Size | Deploy Speed | IAM Permissions Required |
282
+ |------|---------------|--------------|-------------------------|
283
+ | **Layer (default)** | ~45MB per function | Faster (layer cached) | `lambda:PublishLayerVersion` |
284
+ | **Bundled** | ~80MB per function | Slower (Prisma uploaded per function) | None (layer-related) |
285
+
286
+ > ℹ️ When `usePrismaLambdaLayer: false`, Prisma stays inside each bundle and the runtime automatically loads the correct binary. No extra configuration is required.
287
+
260
288
  ## Usage Examples
261
289
 
262
290
  ### Basic Deployment
@@ -57,6 +57,8 @@ class MigrationBuilder extends InfrastructureBuilder {
57
57
  // Backwards compatibility: Translate old schema to new ownership schema
58
58
  appDefinition = this.translateLegacyConfig(appDefinition, discoveredResources);
59
59
 
60
+ const usePrismaLayer = appDefinition.usePrismaLambdaLayer !== false;
61
+
60
62
  const result = {
61
63
  functions: {}, // Lambda function definitions
62
64
  resources: {},
@@ -76,7 +78,7 @@ class MigrationBuilder extends InfrastructureBuilder {
76
78
  console.log(` Queue: ${decisions.queue.ownership} - ${decisions.queue.reason}`);
77
79
 
78
80
  // Build resources based on ownership decisions
79
- await this.buildFromDecisions(decisions, appDefinition, discoveredResources, result);
81
+ await this.buildFromDecisions(decisions, appDefinition, discoveredResources, result, usePrismaLayer);
80
82
 
81
83
  console.log(`[${this.name}] ✅ Migration infrastructure configuration completed`);
82
84
  return result;
@@ -207,7 +209,7 @@ class MigrationBuilder extends InfrastructureBuilder {
207
209
  /**
208
210
  * Build migration resources based on ownership decisions
209
211
  */
210
- async buildFromDecisions(decisions, appDefinition, discoveredResources, result) {
212
+ async buildFromDecisions(decisions, appDefinition, discoveredResources, result, usePrismaLayer = true) {
211
213
  // Determine if we need to create resources or use existing ones
212
214
  const shouldCreateBucket = decisions.bucket.ownership === ResourceOwnership.STACK;
213
215
  const shouldCreateQueue = decisions.queue.ownership === ResourceOwnership.STACK;
@@ -215,16 +217,16 @@ class MigrationBuilder extends InfrastructureBuilder {
215
217
  if (shouldCreateBucket && shouldCreateQueue && !decisions.bucket.physicalId && !decisions.queue.physicalId) {
216
218
  // Create all new migration infrastructure
217
219
  console.log(' → Creating new migration infrastructure in stack');
218
- await this.createMigrationInfrastructure(appDefinition, result);
220
+ await this.createMigrationInfrastructure(appDefinition, result, usePrismaLayer);
219
221
  } else if ((decisions.bucket.ownership === ResourceOwnership.STACK && decisions.bucket.physicalId) ||
220
222
  (decisions.queue.ownership === ResourceOwnership.STACK && decisions.queue.physicalId)) {
221
223
  // Resources exist in stack - add definitions (CloudFormation idempotency)
222
224
  console.log(' → Adding migration definitions to template (existing in stack)');
223
- await this.createMigrationInfrastructure(appDefinition, result);
225
+ await this.createMigrationInfrastructure(appDefinition, result, usePrismaLayer);
224
226
  } else {
225
227
  // Use external resources
226
228
  console.log(' → Using external migration resources');
227
- await this.useExternalMigrationResources(decisions, appDefinition, result);
229
+ await this.useExternalMigrationResources(decisions, appDefinition, result, usePrismaLayer);
228
230
  }
229
231
  }
230
232
 
@@ -232,17 +234,19 @@ class MigrationBuilder extends InfrastructureBuilder {
232
234
  * Create Lambda function definitions for database migrations
233
235
  * Based on refactor/add-better-support-for-commands branch implementation
234
236
  */
235
- async createFunctionDefinitions(result) {
237
+ async createFunctionDefinitions(result, usePrismaLayer = true) {
236
238
  console.log(' 🔍 DEBUG: createFunctionDefinitions called');
237
239
  console.log(' 🔍 DEBUG: result.functions is:', typeof result.functions, result.functions);
238
240
  // Migration WORKER package config (needs Prisma CLI WASM files)
239
241
  const migrationWorkerPackageConfig = {
240
242
  individually: true,
241
243
  exclude: [
242
- // Exclude Prisma runtime client - it's in the Lambda Layer
243
- 'node_modules/@prisma/client/**',
244
- 'node_modules/.prisma/**',
245
- 'node_modules/@friggframework/core/generated/**',
244
+ // Exclude Prisma runtime client when using Lambda Layer (but keep CLI folder)
245
+ ...(usePrismaLayer ? [
246
+ 'node_modules/@prisma/client/**',
247
+ 'node_modules/.prisma/**',
248
+ 'node_modules/@friggframework/core/generated/**',
249
+ ] : []),
246
250
  // But KEEP node_modules/prisma/** (the CLI with WASM)
247
251
 
248
252
  // Exclude ALL nested node_modules
@@ -328,13 +332,15 @@ class MigrationBuilder extends InfrastructureBuilder {
328
332
  const migrationRouterPackageConfig = {
329
333
  individually: true,
330
334
  exclude: [
331
- // Exclude Prisma runtime client - it's in the Lambda Layer
332
- 'node_modules/@prisma/client/**',
333
- 'node_modules/.prisma/**',
334
- 'node_modules/@friggframework/core/generated/**',
335
+ // Exclude Prisma runtime client when using Lambda Layer
336
+ ...(usePrismaLayer ? [
337
+ 'node_modules/@prisma/client/**',
338
+ 'node_modules/.prisma/**',
339
+ 'node_modules/@friggframework/core/generated/**',
340
+ ] : []),
335
341
 
336
- // Router doesn't need Prisma CLI at all
337
- 'node_modules/prisma/**',
342
+ // Router only skips Prisma CLI if Lambda Layer is enabled
343
+ ...(usePrismaLayer ? ['node_modules/prisma/**'] : []),
338
344
 
339
345
  // Exclude ALL nested node_modules
340
346
  'node_modules/**/node_modules/**',
@@ -422,7 +428,7 @@ class MigrationBuilder extends InfrastructureBuilder {
422
428
  console.log(' 🔍 DEBUG: About to create dbMigrationWorker...');
423
429
  result.functions.dbMigrationWorker = {
424
430
  handler: 'node_modules/@friggframework/core/handlers/workers/db-migration.handler',
425
- layers: [{ Ref: 'PrismaLambdaLayer' }], // Use layer for Prisma client runtime
431
+ ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
426
432
  skipEsbuild: true,
427
433
  timeout: 900, // 15 minutes for long migrations
428
434
  memorySize: 1024, // Extra memory for Prisma operations
@@ -484,12 +490,12 @@ class MigrationBuilder extends InfrastructureBuilder {
484
490
  * Create migration infrastructure CloudFormation resources
485
491
  * Creates S3 bucket, SQS queue, and Lambda function definitions
486
492
  */
487
- async createMigrationInfrastructure(appDefinition, result) {
493
+ async createMigrationInfrastructure(appDefinition, result, usePrismaLayer = true) {
488
494
  console.log(' 🔍 DEBUG: createMigrationInfrastructure called');
489
495
  console.log(' 🔍 DEBUG: result object before createFunctionDefinitions:', Object.keys(result));
490
496
 
491
497
  // Create Lambda function definitions first (they reference the queue)
492
- await this.createFunctionDefinitions(result);
498
+ await this.createFunctionDefinitions(result, usePrismaLayer);
493
499
 
494
500
  console.log(' 🔍 DEBUG: result.functions after createFunctionDefinitions:', Object.keys(result.functions || {}));
495
501
 
@@ -614,7 +620,7 @@ class MigrationBuilder extends InfrastructureBuilder {
614
620
  * Use external migration resources (S3 bucket and SQS queue)
615
621
  * Only references external resources - Lambda functions are defined in serverless.yml
616
622
  */
617
- async useExternalMigrationResources(decisions, appDefinition, result) {
623
+ async useExternalMigrationResources(decisions, appDefinition, result, usePrismaLayer = true) {
618
624
  // Reference external bucket
619
625
  const bucketName = decisions.bucket.physicalId;
620
626
  if (!bucketName) {
@@ -290,5 +290,32 @@ describe('MigrationBuilder', () => {
290
290
  expect(builder.getName()).toBe('MigrationBuilder');
291
291
  });
292
292
  });
293
+
294
+ describe('usePrismaLayer configuration', () => {
295
+ const baseAppDefinition = {
296
+ database: {
297
+ postgres: { enable: true },
298
+ },
299
+ };
300
+
301
+ it('includes Prisma layer by default', async () => {
302
+ const result = await builder.build(baseAppDefinition, {});
303
+ expect(result.functions.dbMigrationWorker.layers).toEqual([{ Ref: 'PrismaLambdaLayer' }]);
304
+ });
305
+
306
+ it('omits Prisma layer when disabled', async () => {
307
+ const appDef = { ...baseAppDefinition, usePrismaLambdaLayer: false };
308
+ const result = await builder.build(appDef, {});
309
+ expect(result.functions.dbMigrationWorker.layers).toBeUndefined();
310
+ });
311
+
312
+ it('bundles Prisma runtime when layer disabled', async () => {
313
+ const appDef = { ...baseAppDefinition, usePrismaLambdaLayer: false };
314
+ const result = await builder.build(appDef, {});
315
+ expect(result.functions.dbMigrationWorker.package.exclude).not.toEqual(
316
+ expect.arrayContaining(['node_modules/@prisma/client/**'])
317
+ );
318
+ });
319
+ });
293
320
  });
294
321
 
@@ -62,6 +62,8 @@ class IntegrationBuilder extends InfrastructureBuilder {
62
62
  console.log(`\n[${this.name}] Configuring integrations...`);
63
63
  console.log(` Processing ${appDefinition.integrations.length} integrations...`);
64
64
 
65
+ const usePrismaLayer = appDefinition.usePrismaLambdaLayer !== false;
66
+
65
67
  const result = {
66
68
  functions: {},
67
69
  resources: {},
@@ -87,7 +89,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
87
89
  });
88
90
 
89
91
  // Build resources based on ownership decisions
90
- await this.buildFromDecisions(decisions, appDefinition, result);
92
+ await this.buildFromDecisions(decisions, appDefinition, result, usePrismaLayer);
91
93
 
92
94
  console.log(`[${this.name}] ✅ Integration configuration completed`);
93
95
  return result;
@@ -142,7 +144,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
142
144
  /**
143
145
  * Build integration resources based on ownership decisions
144
146
  */
145
- async buildFromDecisions(decisions, appDefinition, result) {
147
+ async buildFromDecisions(decisions, appDefinition, result, usePrismaLayer = true) {
146
148
  // Create InternalErrorQueue if ownership = STACK
147
149
  const shouldCreateInternalErrorQueue = decisions.internalErrorQueue.ownership === ResourceOwnership.STACK;
148
150
 
@@ -155,7 +157,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
155
157
  }
156
158
 
157
159
  // Create Lambda function definitions and queue resources for each integration
158
- const functionPackageConfig = this.createFunctionPackageConfig();
160
+ const functionPackageConfig = this.createFunctionPackageConfig(usePrismaLayer);
159
161
 
160
162
  for (const integration of appDefinition.integrations) {
161
163
  const integrationName = integration.Definition.name;
@@ -164,7 +166,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
164
166
  console.log(`\n Adding integration: ${integrationName}`);
165
167
 
166
168
  // Create Lambda function definitions (serverless template code)
167
- await this.createFunctionDefinitions(integration, functionPackageConfig, result);
169
+ await this.createFunctionDefinitions(integration, functionPackageConfig, result, usePrismaLayer);
168
170
 
169
171
  // Create or reference SQS queue based on ownership decision
170
172
  const shouldCreateQueue = queueDecision.ownership === ResourceOwnership.STACK;
@@ -182,7 +184,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
182
184
  /**
183
185
  * Create function package exclusion configuration
184
186
  */
185
- createFunctionPackageConfig() {
187
+ createFunctionPackageConfig(usePrismaLayer = true) {
186
188
  return {
187
189
  exclude: [
188
190
  // Exclude AWS SDK (provided by Lambda runtime)
@@ -190,10 +192,12 @@ class IntegrationBuilder extends InfrastructureBuilder {
190
192
  'node_modules/@aws-sdk/**',
191
193
 
192
194
  // Exclude Prisma (provided via Lambda Layer)
193
- 'node_modules/@prisma/**',
194
- 'node_modules/.prisma/**',
195
- 'node_modules/prisma/**',
196
- 'node_modules/@friggframework/core/generated/**',
195
+ ...(usePrismaLayer ? [
196
+ 'node_modules/@prisma/**',
197
+ 'node_modules/.prisma/**',
198
+ 'node_modules/prisma/**',
199
+ 'node_modules/@friggframework/core/generated/**',
200
+ ] : []),
197
201
 
198
202
  // Exclude ALL nested node_modules
199
203
  'node_modules/**/node_modules/**',
@@ -249,7 +253,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
249
253
  * Create Lambda function definitions for an integration
250
254
  * These are serverless framework template function definitions
251
255
  */
252
- async createFunctionDefinitions(integration, functionPackageConfig, result) {
256
+ async createFunctionDefinitions(integration, functionPackageConfig, result, usePrismaLayer = true) {
253
257
  const integrationName = integration.Definition.name;
254
258
 
255
259
  // Add webhook handler if enabled (BEFORE catch-all proxy route)
@@ -263,7 +267,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
263
267
  handler: `node_modules/@friggframework/core/handlers/routers/integration-webhook-routers.handlers.${integrationName}Webhook.handler`,
264
268
  skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
265
269
  package: functionPackageConfig,
266
- layers: [{ Ref: 'PrismaLambdaLayer' }], // Webhook handlers need Prisma for credential lookups
270
+ ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }), // Webhook handlers need Prisma for credential lookups
267
271
  events: [
268
272
  {
269
273
  httpApi: {
@@ -287,7 +291,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
287
291
  handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
288
292
  skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
289
293
  package: functionPackageConfig,
290
- layers: [{ Ref: 'PrismaLambdaLayer' }], // HTTP handlers need Prisma for integration queries
294
+ ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }), // HTTP handlers need Prisma for integration queries
291
295
  events: [
292
296
  {
293
297
  httpApi: {
@@ -305,7 +309,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
305
309
  handler: `node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.${integrationName}.queueWorker`,
306
310
  skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
307
311
  package: functionPackageConfig,
308
- layers: [{ Ref: 'PrismaLambdaLayer' }], // Queue workers need Prisma for database operations
312
+ ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }), // Queue workers need Prisma for database operations
309
313
  reservedConcurrency: 5,
310
314
  events: [
311
315
  {
@@ -662,6 +662,29 @@ describe('IntegrationBuilder', () => {
662
662
  { Ref: 'PrismaLambdaLayer' }
663
663
  ]);
664
664
  });
665
+
666
+ it('should not attach Prisma layer when usePrismaLambdaLayer=false', async () => {
667
+ const appDefinition = {
668
+ usePrismaLambdaLayer: false,
669
+ integrations: [
670
+ {
671
+ Definition: {
672
+ name: 'asana',
673
+ webhooks: true,
674
+ },
675
+ },
676
+ ],
677
+ };
678
+
679
+ const result = await integrationBuilder.build(appDefinition, {});
680
+
681
+ expect(result.functions.asana.layers).toBeUndefined();
682
+ expect(result.functions.asanaQueueWorker.layers).toBeUndefined();
683
+ expect(result.functions.asanaWebhook.layers).toBeUndefined();
684
+ expect(result.functions.asana.package.exclude).not.toEqual(
685
+ expect.arrayContaining(['node_modules/@prisma/**'])
686
+ );
687
+ });
665
688
  });
666
689
  });
667
690