@friggframework/devtools 2.0.0--canary.497.a3f25f9.0 → 2.0.0--canary.490.fd1ef7f.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 +9 -3
  2. package/infrastructure/README.md +28 -0
  3. package/infrastructure/domains/database/migration-builder.js +19 -13
  4. package/infrastructure/domains/database/migration-builder.test.js +57 -0
  5. package/infrastructure/domains/integration/integration-builder.js +19 -14
  6. package/infrastructure/domains/integration/integration-builder.test.js +0 -74
  7. package/infrastructure/domains/networking/vpc-builder.js +240 -18
  8. package/infrastructure/domains/networking/vpc-builder.test.js +711 -13
  9. package/infrastructure/domains/networking/vpc-resolver.js +221 -40
  10. package/infrastructure/domains/networking/vpc-resolver.test.js +318 -18
  11. package/infrastructure/domains/security/kms-builder.js +55 -6
  12. package/infrastructure/domains/security/kms-builder.test.js +19 -1
  13. package/infrastructure/domains/shared/cloudformation-discovery.js +310 -13
  14. package/infrastructure/domains/shared/cloudformation-discovery.test.js +395 -0
  15. package/infrastructure/domains/shared/providers/aws-provider-adapter.js +41 -6
  16. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +39 -0
  17. package/infrastructure/domains/shared/resource-discovery.js +17 -5
  18. package/infrastructure/domains/shared/resource-discovery.test.js +36 -0
  19. package/infrastructure/domains/shared/utilities/base-definition-factory.js +27 -17
  20. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +73 -0
  21. package/infrastructure/infrastructure-composer.js +11 -3
  22. package/infrastructure/scripts/build-prisma-layer.js +8 -81
  23. package/infrastructure/scripts/build-prisma-layer.test.js +1 -53
  24. package/layers/prisma/.build-complete +3 -0
  25. package/package.json +7 -7
  26. package/infrastructure/scripts/verify-prisma-layer.js +0 -72
@@ -284,8 +284,13 @@ async function deployCommand(options) {
284
284
 
285
285
  console.log('\n✓ Deployment completed successfully!');
286
286
 
287
- // Run post-deployment health check (unless --skip-doctor)
288
- if (!options.skipDoctor) {
287
+ // Run post-deployment health check (unless disabled)
288
+ // Can be disabled via:
289
+ // 1. CLI flag: --skip-doctor
290
+ // 2. AppDefinition: deployment.skipPostDeploymentHealthCheck: true
291
+ const skipHealthCheck = options.skipDoctor || appDefinition?.deployment?.skipPostDeploymentHealthCheck;
292
+
293
+ if (!skipHealthCheck) {
289
294
  const stackName = getStackName(appDefinition, options);
290
295
 
291
296
  if (stackName) {
@@ -295,7 +300,8 @@ async function deployCommand(options) {
295
300
  console.log(' Run "frigg doctor <stack-name>" manually to check stack health');
296
301
  }
297
302
  } else {
298
- console.log('\n⏭️ Skipping post-deployment health check (--skip-doctor)');
303
+ const reason = options.skipDoctor ? '--skip-doctor flag' : 'deployment.skipPostDeploymentHealthCheck: true';
304
+ console.log(`\n⏭️ Skipping post-deployment health check (${reason})`);
299
305
  }
300
306
  }
301
307
 
@@ -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, Frigg uses a Lambda Layer for Prisma. You can disable this and bundle Prisma directly with each function:
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
+ - ✅ CI/CD IAM user lacks `lambda:PublishLayerVersion` permission
275
+ - ✅ Deploying to environments with Lambda layer restrictions
276
+ - ✅ Prefer simpler deployment without layer management
277
+ - ✅ Debugging Prisma client loading issues
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 5x) | None (layer-related) |
285
+
286
+ **Note:** When `usePrismaLambdaLayer: false`, Prisma client automatically detects the bundled location at runtime. No additional configuration needed.
287
+
260
288
  ## Usage Examples
261
289
 
262
290
  ### Basic Deployment
@@ -57,6 +57,9 @@ 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
+ // Determine if using Prisma Lambda Layer
61
+ const usePrismaLayer = appDefinition.usePrismaLambdaLayer !== false;
62
+
60
63
  const result = {
61
64
  functions: {}, // Lambda function definitions
62
65
  resources: {},
@@ -76,7 +79,7 @@ class MigrationBuilder extends InfrastructureBuilder {
76
79
  console.log(` Queue: ${decisions.queue.ownership} - ${decisions.queue.reason}`);
77
80
 
78
81
  // Build resources based on ownership decisions
79
- await this.buildFromDecisions(decisions, appDefinition, discoveredResources, result);
82
+ await this.buildFromDecisions(decisions, appDefinition, discoveredResources, result, usePrismaLayer);
80
83
 
81
84
  console.log(`[${this.name}] ✅ Migration infrastructure configuration completed`);
82
85
  return result;
@@ -207,7 +210,7 @@ class MigrationBuilder extends InfrastructureBuilder {
207
210
  /**
208
211
  * Build migration resources based on ownership decisions
209
212
  */
210
- async buildFromDecisions(decisions, appDefinition, discoveredResources, result) {
213
+ async buildFromDecisions(decisions, appDefinition, discoveredResources, result, usePrismaLayer = true) {
211
214
  // Determine if we need to create resources or use existing ones
212
215
  const shouldCreateBucket = decisions.bucket.ownership === ResourceOwnership.STACK;
213
216
  const shouldCreateQueue = decisions.queue.ownership === ResourceOwnership.STACK;
@@ -215,12 +218,12 @@ class MigrationBuilder extends InfrastructureBuilder {
215
218
  if (shouldCreateBucket && shouldCreateQueue && !decisions.bucket.physicalId && !decisions.queue.physicalId) {
216
219
  // Create all new migration infrastructure
217
220
  console.log(' → Creating new migration infrastructure in stack');
218
- await this.createMigrationInfrastructure(appDefinition, result);
221
+ await this.createMigrationInfrastructure(appDefinition, result, usePrismaLayer);
219
222
  } else if ((decisions.bucket.ownership === ResourceOwnership.STACK && decisions.bucket.physicalId) ||
220
223
  (decisions.queue.ownership === ResourceOwnership.STACK && decisions.queue.physicalId)) {
221
224
  // Resources exist in stack - add definitions (CloudFormation idempotency)
222
225
  console.log(' → Adding migration definitions to template (existing in stack)');
223
- await this.createMigrationInfrastructure(appDefinition, result);
226
+ await this.createMigrationInfrastructure(appDefinition, result, usePrismaLayer);
224
227
  } else {
225
228
  // Use external resources
226
229
  console.log(' → Using external migration resources');
@@ -232,18 +235,21 @@ class MigrationBuilder extends InfrastructureBuilder {
232
235
  * Create Lambda function definitions for database migrations
233
236
  * Based on refactor/add-better-support-for-commands branch implementation
234
237
  */
235
- async createFunctionDefinitions(result) {
238
+ async createFunctionDefinitions(result, usePrismaLayer = true) {
236
239
  console.log(' 🔍 DEBUG: createFunctionDefinitions called');
237
240
  console.log(' 🔍 DEBUG: result.functions is:', typeof result.functions, result.functions);
238
241
  // Migration WORKER package config (needs Prisma CLI WASM files)
239
242
  const migrationWorkerPackageConfig = {
240
243
  individually: true,
241
244
  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/**',
246
- // But KEEP node_modules/prisma/** (the CLI with WASM)
245
+ // Conditionally exclude Prisma (only if using layer)
246
+ ...(usePrismaLayer ? [
247
+ // Exclude Prisma runtime client - it's in the Lambda Layer
248
+ 'node_modules/@prisma/client/**',
249
+ 'node_modules/.prisma/**',
250
+ 'node_modules/@friggframework/core/generated/**',
251
+ // But KEEP node_modules/prisma/** (the CLI with WASM)
252
+ ] : []),
247
253
 
248
254
  // Exclude ALL nested node_modules
249
255
  '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' }] }), // Conditionally use layer
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
 
@@ -290,5 +290,62 @@ describe('MigrationBuilder', () => {
290
290
  expect(builder.getName()).toBe('MigrationBuilder');
291
291
  });
292
292
  });
293
+
294
+ describe('usePrismaLayer configuration', () => {
295
+ it('should include Prisma layer in migration worker when usePrismaLayer=true (default)', async () => {
296
+ const appDef = {
297
+ database: {
298
+ postgres: { enable: true },
299
+ },
300
+ usePrismaLambdaLayer: true,
301
+ };
302
+
303
+ const result = await builder.build(appDef, {});
304
+
305
+ expect(result.functions.dbMigrationWorker.layers).toEqual([{ Ref: 'PrismaLambdaLayer' }]);
306
+
307
+ // Prisma client should be excluded from package (but not CLI)
308
+ expect(result.functions.dbMigrationWorker.package.exclude).toEqual(
309
+ expect.arrayContaining([
310
+ 'node_modules/@prisma/client/**',
311
+ 'node_modules/.prisma/**',
312
+ 'node_modules/@friggframework/core/generated/**',
313
+ ])
314
+ );
315
+ });
316
+
317
+ it('should NOT include Prisma layer when usePrismaLayer=false', async () => {
318
+ const appDef = {
319
+ database: {
320
+ postgres: { enable: true },
321
+ },
322
+ usePrismaLambdaLayer: false,
323
+ };
324
+
325
+ const result = await builder.build(appDef, {});
326
+
327
+ expect(result.functions.dbMigrationWorker.layers).toBeUndefined();
328
+ });
329
+
330
+ it('should bundle Prisma CLI with migration worker when usePrismaLayer=false', async () => {
331
+ const appDef = {
332
+ database: {
333
+ postgres: { enable: true },
334
+ },
335
+ usePrismaLambdaLayer: false,
336
+ };
337
+
338
+ const result = await builder.build(appDef, {});
339
+
340
+ // Prisma should NOT be excluded from package (will be bundled)
341
+ expect(result.functions.dbMigrationWorker.package.exclude).not.toEqual(
342
+ expect.arrayContaining([
343
+ 'node_modules/@prisma/client/**',
344
+ 'node_modules/.prisma/**',
345
+ 'node_modules/@friggframework/core/generated/**',
346
+ ])
347
+ );
348
+ });
349
+ });
293
350
  });
294
351
 
@@ -62,6 +62,9 @@ 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
+ // Determine if using Prisma Lambda Layer
66
+ const usePrismaLayer = appDefinition.usePrismaLambdaLayer !== false;
67
+
65
68
  const result = {
66
69
  functions: {},
67
70
  resources: {},
@@ -87,7 +90,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
87
90
  });
88
91
 
89
92
  // Build resources based on ownership decisions
90
- await this.buildFromDecisions(decisions, appDefinition, result);
93
+ await this.buildFromDecisions(decisions, appDefinition, result, usePrismaLayer);
91
94
 
92
95
  console.log(`[${this.name}] ✅ Integration configuration completed`);
93
96
  return result;
@@ -142,7 +145,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
142
145
  /**
143
146
  * Build integration resources based on ownership decisions
144
147
  */
145
- async buildFromDecisions(decisions, appDefinition, result) {
148
+ async buildFromDecisions(decisions, appDefinition, result, usePrismaLayer = true) {
146
149
  // Create InternalErrorQueue if ownership = STACK
147
150
  const shouldCreateInternalErrorQueue = decisions.internalErrorQueue.ownership === ResourceOwnership.STACK;
148
151
 
@@ -155,7 +158,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
155
158
  }
156
159
 
157
160
  // Create Lambda function definitions and queue resources for each integration
158
- const functionPackageConfig = this.createFunctionPackageConfig();
161
+ const functionPackageConfig = this.createFunctionPackageConfig(usePrismaLayer);
159
162
 
160
163
  for (const integration of appDefinition.integrations) {
161
164
  const integrationName = integration.Definition.name;
@@ -164,7 +167,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
164
167
  console.log(`\n Adding integration: ${integrationName}`);
165
168
 
166
169
  // Create Lambda function definitions (serverless template code)
167
- await this.createFunctionDefinitions(integration, functionPackageConfig, result);
170
+ await this.createFunctionDefinitions(integration, functionPackageConfig, result, usePrismaLayer);
168
171
 
169
172
  // Create or reference SQS queue based on ownership decision
170
173
  const shouldCreateQueue = queueDecision.ownership === ResourceOwnership.STACK;
@@ -182,18 +185,20 @@ class IntegrationBuilder extends InfrastructureBuilder {
182
185
  /**
183
186
  * Create function package exclusion configuration
184
187
  */
185
- createFunctionPackageConfig() {
188
+ createFunctionPackageConfig(usePrismaLayer = true) {
186
189
  return {
187
190
  exclude: [
188
191
  // Exclude AWS SDK (provided by Lambda runtime)
189
192
  'node_modules/aws-sdk/**',
190
193
  'node_modules/@aws-sdk/**',
191
194
 
192
- // 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
+ // Conditionally exclude Prisma (only if using Lambda Layer)
196
+ ...(usePrismaLayer ? [
197
+ 'node_modules/@prisma/**',
198
+ 'node_modules/.prisma/**',
199
+ 'node_modules/prisma/**',
200
+ 'node_modules/@friggframework/core/generated/**',
201
+ ] : []),
197
202
 
198
203
  // Exclude ALL nested node_modules
199
204
  'node_modules/**/node_modules/**',
@@ -249,7 +254,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
249
254
  * Create Lambda function definitions for an integration
250
255
  * These are serverless framework template function definitions
251
256
  */
252
- async createFunctionDefinitions(integration, functionPackageConfig, result) {
257
+ async createFunctionDefinitions(integration, functionPackageConfig, result, usePrismaLayer = true) {
253
258
  const integrationName = integration.Definition.name;
254
259
 
255
260
  // Add webhook handler if enabled (BEFORE catch-all proxy route)
@@ -261,9 +266,9 @@ class IntegrationBuilder extends InfrastructureBuilder {
261
266
 
262
267
  result.functions[webhookFunctionName] = {
263
268
  handler: `node_modules/@friggframework/core/handlers/routers/integration-webhook-routers.handlers.${integrationName}Webhook.handler`,
269
+ ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
264
270
  skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
265
271
  package: functionPackageConfig,
266
- layers: [{ Ref: 'PrismaLambdaLayer' }], // Webhook handlers need Prisma for credential lookups
267
272
  events: [
268
273
  {
269
274
  httpApi: {
@@ -285,9 +290,9 @@ class IntegrationBuilder extends InfrastructureBuilder {
285
290
  // Create HTTP API handler for integration (catch-all route AFTER webhooks)
286
291
  result.functions[integrationName] = {
287
292
  handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
293
+ ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
288
294
  skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
289
295
  package: functionPackageConfig,
290
- layers: [{ Ref: 'PrismaLambdaLayer' }], // HTTP handlers need Prisma for integration queries
291
296
  events: [
292
297
  {
293
298
  httpApi: {
@@ -303,9 +308,9 @@ class IntegrationBuilder extends InfrastructureBuilder {
303
308
  const queueWorkerName = `${integrationName}QueueWorker`;
304
309
  result.functions[queueWorkerName] = {
305
310
  handler: `node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.${integrationName}.queueWorker`,
311
+ ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
306
312
  skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
307
313
  package: functionPackageConfig,
308
- layers: [{ Ref: 'PrismaLambdaLayer' }], // Queue workers need Prisma for database operations
309
314
  reservedConcurrency: 5,
310
315
  events: [
311
316
  {
@@ -589,79 +589,5 @@ describe('IntegrationBuilder', () => {
589
589
  expect(result.functions.testWebhook.package.exclude).toContain('node_modules/@prisma/**');
590
590
  });
591
591
  });
592
-
593
- describe('Prisma Layer Configuration', () => {
594
- it('should attach Prisma Lambda layer to queue worker functions', async () => {
595
- const appDefinition = {
596
- integrations: [
597
- { Definition: { name: 'hubspot' } },
598
- ],
599
- };
600
-
601
- const result = await integrationBuilder.build(appDefinition, {});
602
-
603
- // Queue workers need Prisma layer for database operations
604
- expect(result.functions.hubspotQueueWorker.layers).toEqual([
605
- { Ref: 'PrismaLambdaLayer' }
606
- ]);
607
- });
608
-
609
- it('should attach Prisma layer to multiple queue workers', async () => {
610
- const appDefinition = {
611
- integrations: [
612
- { Definition: { name: 'hubspot' } },
613
- { Definition: { name: 'salesforce' } },
614
- { Definition: { name: 'slack' } },
615
- ],
616
- };
617
-
618
- const result = await integrationBuilder.build(appDefinition, {});
619
-
620
- expect(result.functions.hubspotQueueWorker.layers).toEqual([
621
- { Ref: 'PrismaLambdaLayer' }
622
- ]);
623
- expect(result.functions.salesforceQueueWorker.layers).toEqual([
624
- { Ref: 'PrismaLambdaLayer' }
625
- ]);
626
- expect(result.functions.slackQueueWorker.layers).toEqual([
627
- { Ref: 'PrismaLambdaLayer' }
628
- ]);
629
- });
630
-
631
- it('should attach Prisma layer to HTTP handlers for database access', async () => {
632
- const appDefinition = {
633
- integrations: [
634
- { Definition: { name: 'stripe' } },
635
- ],
636
- };
637
-
638
- const result = await integrationBuilder.build(appDefinition, {});
639
-
640
- // HTTP handlers also need Prisma for integration queries
641
- expect(result.functions.stripe.layers).toEqual([
642
- { Ref: 'PrismaLambdaLayer' }
643
- ]);
644
- });
645
-
646
- it('should attach Prisma layer to webhook handlers', async () => {
647
- const appDefinition = {
648
- integrations: [
649
- {
650
- Definition: {
651
- name: 'hubspot',
652
- webhooks: true,
653
- }
654
- },
655
- ],
656
- };
657
-
658
- const result = await integrationBuilder.build(appDefinition, {});
659
-
660
- // Webhook handlers need Prisma for credential lookups
661
- expect(result.functions.hubspotWebhook.layers).toEqual([
662
- { Ref: 'PrismaLambdaLayer' }
663
- ]);
664
- });
665
- });
666
592
  });
667
593