@friggframework/devtools 2.0.0--canary.490.a8050a4.0 → 2.0.0--canary.499.2ef107f.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.
@@ -284,13 +284,8 @@ async function deployCommand(options) {
284
284
 
285
285
  console.log('\n✓ Deployment completed successfully!');
286
286
 
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) {
287
+ // Run post-deployment health check (unless --skip-doctor)
288
+ if (!options.skipDoctor) {
294
289
  const stackName = getStackName(appDefinition, options);
295
290
 
296
291
  if (stackName) {
@@ -300,8 +295,7 @@ async function deployCommand(options) {
300
295
  console.log(' Run "frigg doctor <stack-name>" manually to check stack health');
301
296
  }
302
297
  } else {
303
- const reason = options.skipDoctor ? '--skip-doctor flag' : 'deployment.skipPostDeploymentHealthCheck: true';
304
- console.log(`\n⏭️ Skipping post-deployment health check (${reason})`);
298
+ console.log('\n⏭️ Skipping post-deployment health check (--skip-doctor)');
305
299
  }
306
300
  }
307
301
 
@@ -257,34 +257,6 @@ 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
-
288
260
  ## Usage Examples
289
261
 
290
262
  ### Basic Deployment
@@ -57,9 +57,6 @@ 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
-
63
60
  const result = {
64
61
  functions: {}, // Lambda function definitions
65
62
  resources: {},
@@ -79,7 +76,7 @@ class MigrationBuilder extends InfrastructureBuilder {
79
76
  console.log(` Queue: ${decisions.queue.ownership} - ${decisions.queue.reason}`);
80
77
 
81
78
  // Build resources based on ownership decisions
82
- await this.buildFromDecisions(decisions, appDefinition, discoveredResources, result, usePrismaLayer);
79
+ await this.buildFromDecisions(decisions, appDefinition, discoveredResources, result);
83
80
 
84
81
  console.log(`[${this.name}] ✅ Migration infrastructure configuration completed`);
85
82
  return result;
@@ -210,7 +207,7 @@ class MigrationBuilder extends InfrastructureBuilder {
210
207
  /**
211
208
  * Build migration resources based on ownership decisions
212
209
  */
213
- async buildFromDecisions(decisions, appDefinition, discoveredResources, result, usePrismaLayer = true) {
210
+ async buildFromDecisions(decisions, appDefinition, discoveredResources, result) {
214
211
  // Determine if we need to create resources or use existing ones
215
212
  const shouldCreateBucket = decisions.bucket.ownership === ResourceOwnership.STACK;
216
213
  const shouldCreateQueue = decisions.queue.ownership === ResourceOwnership.STACK;
@@ -218,12 +215,12 @@ class MigrationBuilder extends InfrastructureBuilder {
218
215
  if (shouldCreateBucket && shouldCreateQueue && !decisions.bucket.physicalId && !decisions.queue.physicalId) {
219
216
  // Create all new migration infrastructure
220
217
  console.log(' → Creating new migration infrastructure in stack');
221
- await this.createMigrationInfrastructure(appDefinition, result, usePrismaLayer);
218
+ await this.createMigrationInfrastructure(appDefinition, result);
222
219
  } else if ((decisions.bucket.ownership === ResourceOwnership.STACK && decisions.bucket.physicalId) ||
223
220
  (decisions.queue.ownership === ResourceOwnership.STACK && decisions.queue.physicalId)) {
224
221
  // Resources exist in stack - add definitions (CloudFormation idempotency)
225
222
  console.log(' → Adding migration definitions to template (existing in stack)');
226
- await this.createMigrationInfrastructure(appDefinition, result, usePrismaLayer);
223
+ await this.createMigrationInfrastructure(appDefinition, result);
227
224
  } else {
228
225
  // Use external resources
229
226
  console.log(' → Using external migration resources');
@@ -235,21 +232,18 @@ class MigrationBuilder extends InfrastructureBuilder {
235
232
  * Create Lambda function definitions for database migrations
236
233
  * Based on refactor/add-better-support-for-commands branch implementation
237
234
  */
238
- async createFunctionDefinitions(result, usePrismaLayer = true) {
235
+ async createFunctionDefinitions(result) {
239
236
  console.log(' 🔍 DEBUG: createFunctionDefinitions called');
240
237
  console.log(' 🔍 DEBUG: result.functions is:', typeof result.functions, result.functions);
241
238
  // Migration WORKER package config (needs Prisma CLI WASM files)
242
239
  const migrationWorkerPackageConfig = {
243
240
  individually: true,
244
241
  exclude: [
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
- ] : []),
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)
253
247
 
254
248
  // Exclude ALL nested node_modules
255
249
  'node_modules/**/node_modules/**',
@@ -428,7 +422,7 @@ class MigrationBuilder extends InfrastructureBuilder {
428
422
  console.log(' 🔍 DEBUG: About to create dbMigrationWorker...');
429
423
  result.functions.dbMigrationWorker = {
430
424
  handler: 'node_modules/@friggframework/core/handlers/workers/db-migration.handler',
431
- ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }), // Conditionally use layer
425
+ layers: [{ Ref: 'PrismaLambdaLayer' }], // Use layer for Prisma client runtime
432
426
  skipEsbuild: true,
433
427
  timeout: 900, // 15 minutes for long migrations
434
428
  memorySize: 1024, // Extra memory for Prisma operations
@@ -490,12 +484,12 @@ class MigrationBuilder extends InfrastructureBuilder {
490
484
  * Create migration infrastructure CloudFormation resources
491
485
  * Creates S3 bucket, SQS queue, and Lambda function definitions
492
486
  */
493
- async createMigrationInfrastructure(appDefinition, result, usePrismaLayer = true) {
487
+ async createMigrationInfrastructure(appDefinition, result) {
494
488
  console.log(' 🔍 DEBUG: createMigrationInfrastructure called');
495
489
  console.log(' 🔍 DEBUG: result object before createFunctionDefinitions:', Object.keys(result));
496
490
 
497
491
  // Create Lambda function definitions first (they reference the queue)
498
- await this.createFunctionDefinitions(result, usePrismaLayer);
492
+ await this.createFunctionDefinitions(result);
499
493
 
500
494
  console.log(' 🔍 DEBUG: result.functions after createFunctionDefinitions:', Object.keys(result.functions || {}));
501
495
 
@@ -290,62 +290,5 @@ 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
- });
350
293
  });
351
294
 
@@ -62,9 +62,6 @@ 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
-
68
65
  const result = {
69
66
  functions: {},
70
67
  resources: {},
@@ -90,7 +87,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
90
87
  });
91
88
 
92
89
  // Build resources based on ownership decisions
93
- await this.buildFromDecisions(decisions, appDefinition, result, usePrismaLayer);
90
+ await this.buildFromDecisions(decisions, appDefinition, result);
94
91
 
95
92
  console.log(`[${this.name}] ✅ Integration configuration completed`);
96
93
  return result;
@@ -145,7 +142,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
145
142
  /**
146
143
  * Build integration resources based on ownership decisions
147
144
  */
148
- async buildFromDecisions(decisions, appDefinition, result, usePrismaLayer = true) {
145
+ async buildFromDecisions(decisions, appDefinition, result) {
149
146
  // Create InternalErrorQueue if ownership = STACK
150
147
  const shouldCreateInternalErrorQueue = decisions.internalErrorQueue.ownership === ResourceOwnership.STACK;
151
148
 
@@ -158,7 +155,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
158
155
  }
159
156
 
160
157
  // Create Lambda function definitions and queue resources for each integration
161
- const functionPackageConfig = this.createFunctionPackageConfig(usePrismaLayer);
158
+ const functionPackageConfig = this.createFunctionPackageConfig();
162
159
 
163
160
  for (const integration of appDefinition.integrations) {
164
161
  const integrationName = integration.Definition.name;
@@ -167,7 +164,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
167
164
  console.log(`\n Adding integration: ${integrationName}`);
168
165
 
169
166
  // Create Lambda function definitions (serverless template code)
170
- await this.createFunctionDefinitions(integration, functionPackageConfig, result, usePrismaLayer);
167
+ await this.createFunctionDefinitions(integration, functionPackageConfig, result);
171
168
 
172
169
  // Create or reference SQS queue based on ownership decision
173
170
  const shouldCreateQueue = queueDecision.ownership === ResourceOwnership.STACK;
@@ -185,20 +182,18 @@ class IntegrationBuilder extends InfrastructureBuilder {
185
182
  /**
186
183
  * Create function package exclusion configuration
187
184
  */
188
- createFunctionPackageConfig(usePrismaLayer = true) {
185
+ createFunctionPackageConfig() {
189
186
  return {
190
187
  exclude: [
191
188
  // Exclude AWS SDK (provided by Lambda runtime)
192
189
  'node_modules/aws-sdk/**',
193
190
  'node_modules/@aws-sdk/**',
194
191
 
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
- ] : []),
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/**',
202
197
 
203
198
  // Exclude ALL nested node_modules
204
199
  'node_modules/**/node_modules/**',
@@ -254,7 +249,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
254
249
  * Create Lambda function definitions for an integration
255
250
  * These are serverless framework template function definitions
256
251
  */
257
- async createFunctionDefinitions(integration, functionPackageConfig, result, usePrismaLayer = true) {
252
+ async createFunctionDefinitions(integration, functionPackageConfig, result) {
258
253
  const integrationName = integration.Definition.name;
259
254
 
260
255
  // Add webhook handler if enabled (BEFORE catch-all proxy route)
@@ -266,9 +261,9 @@ class IntegrationBuilder extends InfrastructureBuilder {
266
261
 
267
262
  result.functions[webhookFunctionName] = {
268
263
  handler: `node_modules/@friggframework/core/handlers/routers/integration-webhook-routers.handlers.${integrationName}Webhook.handler`,
269
- ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
270
264
  skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
271
265
  package: functionPackageConfig,
266
+ layers: [{ Ref: 'PrismaLambdaLayer' }], // Webhook handlers need Prisma for credential lookups
272
267
  events: [
273
268
  {
274
269
  httpApi: {
@@ -290,9 +285,9 @@ class IntegrationBuilder extends InfrastructureBuilder {
290
285
  // Create HTTP API handler for integration (catch-all route AFTER webhooks)
291
286
  result.functions[integrationName] = {
292
287
  handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
293
- ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
294
288
  skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
295
289
  package: functionPackageConfig,
290
+ layers: [{ Ref: 'PrismaLambdaLayer' }], // HTTP handlers need Prisma for integration queries
296
291
  events: [
297
292
  {
298
293
  httpApi: {
@@ -308,9 +303,9 @@ class IntegrationBuilder extends InfrastructureBuilder {
308
303
  const queueWorkerName = `${integrationName}QueueWorker`;
309
304
  result.functions[queueWorkerName] = {
310
305
  handler: `node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.${integrationName}.queueWorker`,
311
- ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
312
306
  skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
313
307
  package: functionPackageConfig,
308
+ layers: [{ Ref: 'PrismaLambdaLayer' }], // Queue workers need Prisma for database operations
314
309
  reservedConcurrency: 5,
315
310
  events: [
316
311
  {
@@ -589,5 +589,79 @@ 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
+ });
592
666
  });
593
667
 
@@ -16,20 +16,18 @@ const { buildEnvironment } = require('../environment-builder');
16
16
  * Frigg applications need, including:
17
17
  * - Core Lambda functions (auth, user, health, dbMigrate)
18
18
  * - Error handling infrastructure (SQS, SNS, CloudWatch)
19
- * - Prisma Lambda Layer (optional, controlled by usePrismaLayer)
19
+ * - Prisma Lambda Layer
20
20
  * - Base plugins and esbuild configuration
21
21
  *
22
22
  * @param {Object} AppDefinition - Application definition
23
23
  * @param {Object} appEnvironmentVars - Environment variables from app definition
24
24
  * @param {Object} discoveredResources - AWS resources discovered during build
25
- * @param {boolean} usePrismaLayer - Whether to use Prisma Lambda Layer (default true)
26
25
  * @returns {Object} Base serverless definition
27
26
  */
28
27
  function createBaseDefinition(
29
28
  AppDefinition,
30
29
  appEnvironmentVars,
31
- discoveredResources,
32
- usePrismaLayer = true
30
+ discoveredResources
33
31
  ) {
34
32
  const region = process.env.AWS_REGION || 'us-east-1';
35
33
 
@@ -44,13 +42,11 @@ function createBaseDefinition(
44
42
  : []),
45
43
  ],
46
44
  exclude: [
47
- // Conditionally exclude Prisma (only if using Lambda Layer)
48
- ...(usePrismaLayer ? [
49
- 'node_modules/@prisma/**',
50
- 'node_modules/.prisma/**',
51
- 'node_modules/prisma/**',
52
- 'node_modules/@friggframework/core/generated/**',
53
- ] : []),
45
+ // Exclude Prisma (provided via Lambda Layer)
46
+ 'node_modules/@prisma/**',
47
+ 'node_modules/.prisma/**',
48
+ 'node_modules/prisma/**',
49
+ 'node_modules/@friggframework/core/generated/**',
54
50
 
55
51
  // Exclude AWS SDK (provided by Lambda runtime)
56
52
  'node_modules/aws-sdk/**',
@@ -222,12 +218,9 @@ function createBaseDefinition(
222
218
  external: [
223
219
  '@aws-sdk/*',
224
220
  'aws-sdk',
225
- // Conditionally mark Prisma as external (only if using layer)
226
- ...(usePrismaLayer ? [
227
- '@prisma/client',
228
- 'prisma',
229
- '.prisma/*',
230
- ] : []),
221
+ '@prisma/client',
222
+ 'prisma',
223
+ '.prisma/*',
231
224
  ],
232
225
  packager: 'npm',
233
226
  keepNames: true,
@@ -235,11 +228,8 @@ function createBaseDefinition(
235
228
  exclude: [
236
229
  'aws-sdk',
237
230
  '@aws-sdk/*',
238
- // Conditionally exclude Prisma (only if using layer)
239
- ...(usePrismaLayer ? [
240
- '@prisma/client',
241
- 'prisma',
242
- ] : []),
231
+ '@prisma/client',
232
+ 'prisma',
243
233
  ],
244
234
  },
245
235
  'serverless-offline': {
@@ -262,7 +252,7 @@ function createBaseDefinition(
262
252
  functions: {
263
253
  auth: {
264
254
  handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
265
- ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
255
+ layers: [{ Ref: 'PrismaLambdaLayer' }],
266
256
  skipEsbuild: true, // Handlers in node_modules don't need bundling
267
257
  package: skipEsbuildPackageConfig,
268
258
  events: [
@@ -278,14 +268,14 @@ function createBaseDefinition(
278
268
  },
279
269
  user: {
280
270
  handler: 'node_modules/@friggframework/core/handlers/routers/user.handler',
281
- ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
271
+ layers: [{ Ref: 'PrismaLambdaLayer' }],
282
272
  skipEsbuild: true, // Handlers in node_modules don't need bundling
283
273
  package: skipEsbuildPackageConfig,
284
274
  events: [{ httpApi: { path: '/user/{proxy+}', method: 'ANY' } }],
285
275
  },
286
276
  health: {
287
277
  handler: 'node_modules/@friggframework/core/handlers/routers/health.handler',
288
- ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
278
+ layers: [{ Ref: 'PrismaLambdaLayer' }],
289
279
  skipEsbuild: true, // Handlers in node_modules don't need bundling
290
280
  package: skipEsbuildPackageConfig,
291
281
  events: [
@@ -296,7 +286,7 @@ function createBaseDefinition(
296
286
  // Note: dbMigrate removed - MigrationBuilder now handles migration infrastructure
297
287
  // See: packages/devtools/infrastructure/domains/database/migration-builder.js
298
288
  },
299
- layers: usePrismaLayer ? {
289
+ layers: {
300
290
  prisma: {
301
291
  path: 'layers/prisma',
302
292
  name: '${self:service}-prisma-${sls:stage}',
@@ -304,7 +294,7 @@ function createBaseDefinition(
304
294
  compatibleRuntimes: ['nodejs20.x', 'nodejs22.x'],
305
295
  retain: false,
306
296
  },
307
- } : {},
297
+ },
308
298
  resources: {
309
299
  Resources: {
310
300
  InternalErrorQueue: {
@@ -243,79 +243,6 @@ describe('Base Definition Factory', () => {
243
243
  expect(result.useDotenv).toBeDefined();
244
244
  expect(typeof result.useDotenv).toBe('boolean');
245
245
  });
246
-
247
- describe('usePrismaLayer configuration', () => {
248
- it('should include Prisma layer by default (usePrismaLayer=true)', () => {
249
- const result = createBaseDefinition({}, {}, {}, true);
250
-
251
- // Layer definition should exist
252
- expect(result.layers.prisma).toBeDefined();
253
- expect(result.layers.prisma.path).toBe('layers/prisma');
254
-
255
- // Functions should reference the layer
256
- expect(result.functions.auth.layers).toEqual([{ Ref: 'PrismaLambdaLayer' }]);
257
- expect(result.functions.user.layers).toEqual([{ Ref: 'PrismaLambdaLayer' }]);
258
- expect(result.functions.health.layers).toEqual([{ Ref: 'PrismaLambdaLayer' }]);
259
-
260
- // Prisma should be excluded from packages
261
- expect(result.functions.auth.package.exclude).toEqual(
262
- expect.arrayContaining([
263
- 'node_modules/@prisma/**',
264
- 'node_modules/.prisma/**',
265
- 'node_modules/prisma/**',
266
- 'node_modules/@friggframework/core/generated/**',
267
- ])
268
- );
269
-
270
- // Prisma should be external in esbuild
271
- expect(result.custom.esbuild.external).toContain('@prisma/client');
272
- expect(result.custom.esbuild.external).toContain('prisma');
273
- expect(result.custom.esbuild.exclude).toContain('@prisma/client');
274
- expect(result.custom.esbuild.exclude).toContain('prisma');
275
- });
276
-
277
- it('should NOT include Prisma layer when usePrismaLayer=false', () => {
278
- const result = createBaseDefinition({}, {}, {}, false);
279
-
280
- // Layer definition should NOT exist
281
- expect(result.layers).toEqual({});
282
-
283
- // Functions should NOT have layer references
284
- expect(result.functions.auth.layers).toBeUndefined();
285
- expect(result.functions.user.layers).toBeUndefined();
286
- expect(result.functions.health.layers).toBeUndefined();
287
- });
288
-
289
- it('should bundle Prisma with functions when usePrismaLayer=false', () => {
290
- const result = createBaseDefinition({}, {}, {}, false);
291
-
292
- // Prisma should NOT be excluded from packages
293
- expect(result.functions.auth.package.exclude).not.toEqual(
294
- expect.arrayContaining([
295
- 'node_modules/@prisma/**',
296
- 'node_modules/.prisma/**',
297
- 'node_modules/prisma/**',
298
- 'node_modules/@friggframework/core/generated/**',
299
- ])
300
- );
301
-
302
- // Prisma should NOT be external in esbuild
303
- expect(result.custom.esbuild.external).not.toContain('@prisma/client');
304
- expect(result.custom.esbuild.external).not.toContain('prisma');
305
- expect(result.custom.esbuild.external).not.toContain('.prisma/*');
306
- expect(result.custom.esbuild.exclude).not.toContain('@prisma/client');
307
- expect(result.custom.esbuild.exclude).not.toContain('prisma');
308
- });
309
-
310
- it('should default to usePrismaLayer=true when parameter not provided', () => {
311
- // Call without 4th parameter
312
- const result = createBaseDefinition({}, {}, {});
313
-
314
- // Should behave as if usePrismaLayer=true
315
- expect(result.layers.prisma).toBeDefined();
316
- expect(result.functions.auth.layers).toEqual([{ Ref: 'PrismaLambdaLayer' }]);
317
- });
318
- });
319
246
  });
320
247
  });
321
248
 
@@ -32,15 +32,8 @@ const { validateAndCleanPlugins, validatePackagingConfiguration } = require('./d
32
32
  const composeServerlessDefinition = async (AppDefinition) => {
33
33
  console.log('🏗️ Composing serverless definition with domain builders...');
34
34
 
35
- // Determine if using Prisma Lambda Layer (default true)
36
- const usePrismaLayer = AppDefinition.usePrismaLambdaLayer !== false;
37
-
38
- // Ensure Prisma layer exists only if using layer mode
39
- if (usePrismaLayer) {
40
- await ensurePrismaLayerExists(AppDefinition.database || {});
41
- } else {
42
- console.log('📦 Skipping Prisma Lambda Layer (usePrismaLambdaLayer=false - will bundle with functions)');
43
- }
35
+ // Ensure Prisma layer exists (minimal, runtime only)
36
+ await ensurePrismaLayerExists(AppDefinition.database || {});
44
37
 
45
38
  // Create orchestrator with all domain builders
46
39
  const orchestrator = new BuilderOrchestrator([
@@ -62,8 +55,7 @@ const composeServerlessDefinition = async (AppDefinition) => {
62
55
  const definition = createBaseDefinition(
63
56
  AppDefinition,
64
57
  appEnvironmentVars,
65
- discoveredResources,
66
- usePrismaLayer
58
+ discoveredResources
67
59
  );
68
60
 
69
61
  // Merge builder results into definition
@@ -10,8 +10,8 @@
10
10
  * The CLI is only needed for migrations and is packaged separately with the dbMigrate function.
11
11
  *
12
12
  * The layer is configured based on AppDefinition database settings:
13
- * - PostgreSQL: Includes PostgreSQL client + query engine only
14
- * - MongoDB: Includes MongoDB client + query engine only (if needed)
13
+ * - PostgreSQL: Includes PostgreSQL client + query engine + migrations
14
+ * - MongoDB: Includes MongoDB client + query engine + migrations (if needed)
15
15
  * - Defaults to PostgreSQL only if not specified
16
16
  *
17
17
  * Usage:
@@ -22,7 +22,11 @@
22
22
  * layers/prisma/nodejs/node_modules/
23
23
  * ├── @prisma/client (runtime only, ~10-15MB)
24
24
  * ├── generated/prisma-postgresql (if PostgreSQL enabled)
25
+ * │ ├── schema.prisma
26
+ * │ └── migrations/
25
27
  * └── generated/prisma-mongodb (if MongoDB enabled)
28
+ * ├── schema.prisma
29
+ * └── migrations/
26
30
  *
27
31
  * See: LAMBDA-LAYER-PRISMA.md for complete documentation
28
32
  */
@@ -85,6 +89,27 @@ function getGeneratedClientPackages(databaseConfig = {}) {
85
89
  return packages;
86
90
  }
87
91
 
92
+ function getMigrationsPackages(clientPackages) {
93
+ return clientPackages.map(pkg => ({
94
+ dbType: pkg.replace('generated/prisma-', ''),
95
+ clientPackage: pkg
96
+ }));
97
+ }
98
+
99
+ function getMigrationSourcePath(searchPaths, dbType) {
100
+ for (const searchPath of searchPaths) {
101
+ const candidatePath = path.join(searchPath, `prisma-${dbType}`, 'migrations');
102
+ if (fs.existsSync(candidatePath)) {
103
+ return candidatePath;
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+
109
+ function getMigrationDestinationPath(layerNodeModules, clientPackage) {
110
+ return path.join(layerNodeModules, clientPackage, 'migrations');
111
+ }
112
+
88
113
  // Configuration
89
114
  // Script runs from integration project root (e.g., backend/)
90
115
  // and reads Prisma packages from @friggframework/core
@@ -299,11 +324,47 @@ async function copyPrismaPackages(clientPackages) {
299
324
  logSuccess(`Copied ${copiedCount} generated client packages from @friggframework/core`);
300
325
  }
301
326
 
327
+ async function copyMigrations(clientPackages) {
328
+ logStep(5, 'Copying migrations from @friggframework/core');
329
+
330
+ const workspaceNodeModules = path.join(path.dirname(CORE_PACKAGE_PATH), '..');
331
+ const searchPaths = [
332
+ path.join(CORE_PACKAGE_PATH, 'node_modules'),
333
+ path.join(PROJECT_ROOT, 'node_modules'),
334
+ workspaceNodeModules,
335
+ CORE_PACKAGE_PATH,
336
+ ];
337
+
338
+ const migrations = getMigrationsPackages(clientPackages);
339
+ let copiedCount = 0;
340
+
341
+ for (const { dbType, clientPackage } of migrations) {
342
+ const sourcePath = getMigrationSourcePath(searchPaths, dbType);
343
+
344
+ if (sourcePath) {
345
+ const destPath = getMigrationDestinationPath(LAYER_NODE_MODULES, clientPackage);
346
+ await fs.copy(sourcePath, destPath, { dereference: true });
347
+
348
+ const fromLocation = sourcePath.includes('@friggframework/core/prisma')
349
+ ? 'core package'
350
+ : 'workspace';
351
+ logSuccess(`Copied migrations for ${dbType} (from ${fromLocation})`);
352
+ copiedCount++;
353
+ } else {
354
+ logWarning(`Migrations not found for ${dbType} - this may cause migration failures`);
355
+ }
356
+ }
357
+
358
+ if (copiedCount > 0) {
359
+ logSuccess(`Copied migrations for ${copiedCount} database ${copiedCount === 1 ? 'type' : 'types'}`);
360
+ }
361
+ }
362
+
302
363
  /**
303
364
  * Remove unnecessary files to reduce layer size
304
365
  */
305
366
  async function removeUnnecessaryFiles() {
306
- logStep(5, 'Removing unnecessary files (source maps, docs, tests)');
367
+ logStep(6, 'Removing unnecessary files (source maps, docs, tests)');
307
368
 
308
369
  let removedCount = 0;
309
370
  let totalSize = 0;
@@ -341,7 +402,7 @@ async function removeUnnecessaryFiles() {
341
402
  * Remove non-rhel engine binaries to reduce layer size
342
403
  */
343
404
  async function removeNonRhelBinaries() {
344
- logStep(6, 'Removing non-rhel engine binaries');
405
+ logStep(7, 'Removing non-rhel engine binaries');
345
406
 
346
407
  let removedCount = 0;
347
408
  let totalSize = 0;
@@ -380,7 +441,7 @@ async function removeNonRhelBinaries() {
380
441
  * @param {Array} expectedClients - List of client packages that should have binaries
381
442
  */
382
443
  async function verifyRhelBinaries(expectedClients) {
383
- logStep(7, 'Verifying rhel-openssl-3.0.x binaries are present');
444
+ logStep(8, 'Verifying rhel-openssl-3.0.x binaries are present');
384
445
 
385
446
  try {
386
447
  const findCmd = `find "${LAYER_NODE_MODULES}" -name "*rhel-openssl-3.0.x*" 2>/dev/null || true`;
@@ -414,7 +475,7 @@ async function verifyRhelBinaries(expectedClients) {
414
475
  * @param {Array} clientPackages - Generated client packages that were included
415
476
  */
416
477
  async function verifyLayerStructure(clientPackages) {
417
- logStep(8, 'Verifying layer structure (runtime only)');
478
+ logStep(9, 'Verifying layer structure (runtime only)');
418
479
 
419
480
  const requiredPaths = [
420
481
  '@prisma/client/runtime',
@@ -426,6 +487,11 @@ async function verifyLayerStructure(clientPackages) {
426
487
  requiredPaths.push(`${pkg}/schema.prisma`);
427
488
  }
428
489
 
490
+ // Add migrations directory for each included client
491
+ for (const pkg of clientPackages) {
492
+ requiredPaths.push(`${pkg}/migrations/migration_lock.toml`);
493
+ }
494
+
429
495
  // Verify CLI is NOT present (keeps layer small)
430
496
  const forbiddenPaths = [
431
497
  'prisma/build',
@@ -463,7 +529,7 @@ async function verifyLayerStructure(clientPackages) {
463
529
  * Calculate and display final layer size
464
530
  */
465
531
  async function displayLayerSummary() {
466
- logStep(9, 'Layer build summary');
532
+ logStep(10, 'Layer build summary');
467
533
 
468
534
  const layerSizeMB = getDirectorySize(LAYER_OUTPUT_PATH);
469
535
 
@@ -514,6 +580,7 @@ async function buildPrismaLayer(databaseConfig = {}) {
514
580
  await createLayerStructure();
515
581
  await installPrismaPackages(); // Install runtime client only (NO CLI)
516
582
  await copyPrismaPackages(clientPackages); // Copy generated clients from core
583
+ await copyMigrations(clientPackages); // Copy migrations from core
517
584
  await removeUnnecessaryFiles(); // Remove source maps, docs, tests (37MB+)
518
585
  await removeNonRhelBinaries(); // Remove non-Linux binaries
519
586
  await verifyRhelBinaries(clientPackages); // Verify query engines present
@@ -550,4 +617,10 @@ if (require.main === module) {
550
617
  .catch(() => process.exit(1));
551
618
  }
552
619
 
553
- module.exports = { buildPrismaLayer, getGeneratedClientPackages };
620
+ module.exports = {
621
+ buildPrismaLayer,
622
+ getGeneratedClientPackages,
623
+ getMigrationsPackages,
624
+ getMigrationSourcePath,
625
+ getMigrationDestinationPath
626
+ };
@@ -3,7 +3,12 @@
3
3
  * Validates database client selection logic
4
4
  */
5
5
 
6
- const { getGeneratedClientPackages } = require('./build-prisma-layer');
6
+ const {
7
+ getGeneratedClientPackages,
8
+ getMigrationsPackages,
9
+ getMigrationSourcePath,
10
+ getMigrationDestinationPath
11
+ } = require('./build-prisma-layer');
7
12
 
8
13
  // Mock the log function
9
14
  jest.mock('./build-prisma-layer', () => {
@@ -11,6 +16,9 @@ jest.mock('./build-prisma-layer', () => {
11
16
  return {
12
17
  ...actual,
13
18
  getGeneratedClientPackages: actual.getGeneratedClientPackages,
19
+ getMigrationsPackages: actual.getMigrationsPackages,
20
+ getMigrationSourcePath: actual.getMigrationSourcePath,
21
+ getMigrationDestinationPath: actual.getMigrationDestinationPath,
14
22
  };
15
23
  });
16
24
 
@@ -99,4 +107,48 @@ describe('getGeneratedClientPackages()', () => {
99
107
  });
100
108
  });
101
109
 
110
+ describe('getMigrationsPackages()', () => {
111
+ it('should extract database types from client packages', () => {
112
+ const clientPackages = ['generated/prisma-postgresql', 'generated/prisma-mongodb'];
113
+ const migrations = getMigrationsPackages(clientPackages);
102
114
 
115
+ expect(migrations).toEqual([
116
+ { dbType: 'postgresql', clientPackage: 'generated/prisma-postgresql' },
117
+ { dbType: 'mongodb', clientPackage: 'generated/prisma-mongodb' }
118
+ ]);
119
+ });
120
+
121
+ it('should handle single client package', () => {
122
+ const clientPackages = ['generated/prisma-postgresql'];
123
+ const migrations = getMigrationsPackages(clientPackages);
124
+
125
+ expect(migrations).toEqual([
126
+ { dbType: 'postgresql', clientPackage: 'generated/prisma-postgresql' }
127
+ ]);
128
+ });
129
+
130
+ it('should handle empty array', () => {
131
+ const migrations = getMigrationsPackages([]);
132
+ expect(migrations).toEqual([]);
133
+ });
134
+ });
135
+
136
+ describe('getMigrationSourcePath()', () => {
137
+ it('should return correct source path for database type', () => {
138
+ const searchPaths = ['/workspace/packages/core'];
139
+ const dbType = 'postgresql';
140
+
141
+ const sourcePath = getMigrationSourcePath(searchPaths, dbType);
142
+ expect(sourcePath).toBe('/workspace/packages/core/prisma-postgresql/migrations');
143
+ });
144
+ });
145
+
146
+ describe('getMigrationDestinationPath()', () => {
147
+ it('should return correct destination path for client package', () => {
148
+ const layerNodeModules = '/layers/prisma/nodejs/node_modules';
149
+ const clientPackage = 'generated/prisma-postgresql';
150
+
151
+ const destPath = getMigrationDestinationPath(layerNodeModules, clientPackage);
152
+ expect(destPath).toBe('/layers/prisma/nodejs/node_modules/generated/prisma-postgresql/migrations');
153
+ });
154
+ });
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs-extra');
4
+ const path = require('path');
5
+
6
+ const PROJECT_ROOT = process.cwd();
7
+ const LAYER_PATH = path.join(PROJECT_ROOT, 'layers/prisma/nodejs/node_modules');
8
+
9
+ async function verifyLayerStructure() {
10
+ console.log('Verifying Prisma layer structure...\n');
11
+
12
+ const checks = [
13
+ {
14
+ name: 'PostgreSQL schema',
15
+ path: 'generated/prisma-postgresql/schema.prisma'
16
+ },
17
+ {
18
+ name: 'PostgreSQL migrations directory',
19
+ path: 'generated/prisma-postgresql/migrations'
20
+ },
21
+ {
22
+ name: 'PostgreSQL migration_lock.toml',
23
+ path: 'generated/prisma-postgresql/migrations/migration_lock.toml'
24
+ },
25
+ {
26
+ name: '@prisma/client runtime',
27
+ path: '@prisma/client/runtime'
28
+ }
29
+ ];
30
+
31
+ let allPassed = true;
32
+
33
+ for (const check of checks) {
34
+ const fullPath = path.join(LAYER_PATH, check.path);
35
+ const exists = await fs.pathExists(fullPath);
36
+
37
+ if (exists) {
38
+ console.log(`✓ ${check.name}`);
39
+ } else {
40
+ console.log(`✗ ${check.name} (missing)`);
41
+ allPassed = false;
42
+ }
43
+ }
44
+
45
+ console.log('\n');
46
+
47
+ if (allPassed) {
48
+ console.log('✓ All checks passed!');
49
+ const migrationsPath = path.join(LAYER_PATH, 'generated/prisma-postgresql/migrations');
50
+ const migrationFiles = await fs.readdir(migrationsPath);
51
+ console.log(`\nFound ${migrationFiles.length} items in migrations directory:`);
52
+ migrationFiles.forEach(file => {
53
+ console.log(` - ${file}`);
54
+ });
55
+ return 0;
56
+ } else {
57
+ console.log('✗ Some checks failed!');
58
+ return 1;
59
+ }
60
+ }
61
+
62
+ if (require.main === module) {
63
+ verifyLayerStructure()
64
+ .then(code => process.exit(code))
65
+ .catch(err => {
66
+ console.error('Error:', err.message);
67
+ process.exit(1);
68
+ });
69
+ }
70
+
71
+ module.exports = { verifyLayerStructure };
72
+
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/devtools",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.490.a8050a4.0",
4
+ "version": "2.0.0--canary.499.2ef107f.0",
5
5
  "bin": {
6
6
  "frigg": "./frigg-cli/index.js"
7
7
  },
@@ -16,9 +16,9 @@
16
16
  "@babel/eslint-parser": "^7.18.9",
17
17
  "@babel/parser": "^7.25.3",
18
18
  "@babel/traverse": "^7.25.3",
19
- "@friggframework/core": "2.0.0--canary.490.a8050a4.0",
20
- "@friggframework/schemas": "2.0.0--canary.490.a8050a4.0",
21
- "@friggframework/test": "2.0.0--canary.490.a8050a4.0",
19
+ "@friggframework/core": "2.0.0--canary.499.2ef107f.0",
20
+ "@friggframework/schemas": "2.0.0--canary.499.2ef107f.0",
21
+ "@friggframework/test": "2.0.0--canary.499.2ef107f.0",
22
22
  "@hapi/boom": "^10.0.1",
23
23
  "@inquirer/prompts": "^5.3.8",
24
24
  "axios": "^1.7.2",
@@ -46,8 +46,8 @@
46
46
  "validate-npm-package-name": "^5.0.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@friggframework/eslint-config": "2.0.0--canary.490.a8050a4.0",
50
- "@friggframework/prettier-config": "2.0.0--canary.490.a8050a4.0",
49
+ "@friggframework/eslint-config": "2.0.0--canary.499.2ef107f.0",
50
+ "@friggframework/prettier-config": "2.0.0--canary.499.2ef107f.0",
51
51
  "aws-sdk-client-mock": "^4.1.0",
52
52
  "aws-sdk-client-mock-jest": "^4.1.0",
53
53
  "jest": "^30.1.3",
@@ -79,5 +79,5 @@
79
79
  "publishConfig": {
80
80
  "access": "public"
81
81
  },
82
- "gitHead": "a8050a4942d1756d6b981b2eb05ef002a638ce02"
82
+ "gitHead": "2ef107f34b011aef53330f9c5e4f79316c8fcf3b"
83
83
  }
@@ -1,3 +0,0 @@
1
- Build completed: 2025-10-31T04:18:27.501Z
2
- Node: v23.5.0
3
- Platform: darwin