@friggframework/devtools 2.0.0-next.52 → 2.0.0-next.54

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 -10
  13. package/infrastructure/domains/integration/integration-builder.test.js +97 -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 +2 -2
  32. package/package.json +7 -7
@@ -16,18 +16,20 @@ 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
19
+ * - Prisma Lambda Layer (optional)
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 the Prisma Lambda Layer (default true)
25
26
  * @returns {Object} Base serverless definition
26
27
  */
27
28
  function createBaseDefinition(
28
29
  AppDefinition,
29
30
  appEnvironmentVars,
30
- discoveredResources
31
+ discoveredResources,
32
+ usePrismaLayer = true
31
33
  ) {
32
34
  const region = process.env.AWS_REGION || 'us-east-1';
33
35
 
@@ -43,10 +45,12 @@ function createBaseDefinition(
43
45
  ],
44
46
  exclude: [
45
47
  // Exclude Prisma (provided via Lambda Layer)
46
- 'node_modules/@prisma/**',
47
- 'node_modules/.prisma/**',
48
- 'node_modules/prisma/**',
49
- 'node_modules/@friggframework/core/generated/**',
48
+ ...(usePrismaLayer ? [
49
+ 'node_modules/@prisma/**',
50
+ 'node_modules/.prisma/**',
51
+ 'node_modules/prisma/**',
52
+ 'node_modules/@friggframework/core/generated/**',
53
+ ] : []),
50
54
 
51
55
  // Exclude AWS SDK (provided by Lambda runtime)
52
56
  'node_modules/aws-sdk/**',
@@ -109,10 +113,12 @@ function createBaseDefinition(
109
113
  'node_modules/@aws-sdk/**',
110
114
 
111
115
  // Exclude Prisma (provided via Lambda Layer)
112
- 'node_modules/@prisma/**',
113
- 'node_modules/.prisma/**',
114
- 'node_modules/prisma/**',
115
- 'node_modules/@friggframework/core/generated/**',
116
+ ...(usePrismaLayer ? [
117
+ 'node_modules/@prisma/**',
118
+ 'node_modules/.prisma/**',
119
+ 'node_modules/prisma/**',
120
+ 'node_modules/@friggframework/core/generated/**',
121
+ ] : []),
116
122
 
117
123
  // Exclude nested node_modules from symlinked frigg packages (for npm link development)
118
124
  'node_modules/@friggframework/core/node_modules/**',
@@ -218,9 +224,11 @@ function createBaseDefinition(
218
224
  external: [
219
225
  '@aws-sdk/*',
220
226
  'aws-sdk',
221
- '@prisma/client',
222
- 'prisma',
223
- '.prisma/*',
227
+ ...(usePrismaLayer ? [
228
+ '@prisma/client',
229
+ 'prisma',
230
+ '.prisma/*',
231
+ ] : []),
224
232
  ],
225
233
  packager: 'npm',
226
234
  keepNames: true,
@@ -228,8 +236,10 @@ function createBaseDefinition(
228
236
  exclude: [
229
237
  'aws-sdk',
230
238
  '@aws-sdk/*',
231
- '@prisma/client',
232
- 'prisma',
239
+ ...(usePrismaLayer ? [
240
+ '@prisma/client',
241
+ 'prisma',
242
+ ] : []),
233
243
  ],
234
244
  },
235
245
  'serverless-offline': {
@@ -252,7 +262,7 @@ function createBaseDefinition(
252
262
  functions: {
253
263
  auth: {
254
264
  handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
255
- layers: [{ Ref: 'PrismaLambdaLayer' }],
265
+ ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
256
266
  skipEsbuild: true, // Handlers in node_modules don't need bundling
257
267
  package: skipEsbuildPackageConfig,
258
268
  events: [
@@ -268,14 +278,14 @@ function createBaseDefinition(
268
278
  },
269
279
  user: {
270
280
  handler: 'node_modules/@friggframework/core/handlers/routers/user.handler',
271
- layers: [{ Ref: 'PrismaLambdaLayer' }],
281
+ ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
272
282
  skipEsbuild: true, // Handlers in node_modules don't need bundling
273
283
  package: skipEsbuildPackageConfig,
274
284
  events: [{ httpApi: { path: '/user/{proxy+}', method: 'ANY' } }],
275
285
  },
276
286
  health: {
277
287
  handler: 'node_modules/@friggframework/core/handlers/routers/health.handler',
278
- layers: [{ Ref: 'PrismaLambdaLayer' }],
288
+ ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
279
289
  skipEsbuild: true, // Handlers in node_modules don't need bundling
280
290
  package: skipEsbuildPackageConfig,
281
291
  events: [
@@ -286,7 +296,7 @@ function createBaseDefinition(
286
296
  // Note: dbMigrate removed - MigrationBuilder now handles migration infrastructure
287
297
  // See: packages/devtools/infrastructure/domains/database/migration-builder.js
288
298
  },
289
- layers: {
299
+ layers: usePrismaLayer ? {
290
300
  prisma: {
291
301
  path: 'layers/prisma',
292
302
  name: '${self:service}-prisma-${sls:stage}',
@@ -294,7 +304,7 @@ function createBaseDefinition(
294
304
  compatibleRuntimes: ['nodejs20.x', 'nodejs22.x'],
295
305
  retain: false,
296
306
  },
297
- },
307
+ } : {},
298
308
  resources: {
299
309
  Resources: {
300
310
  InternalErrorQueue: {
@@ -243,6 +243,49 @@ 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('includes Prisma layer and exclusions by default', () => {
249
+ const result = createBaseDefinition({}, {}, {});
250
+
251
+ expect(result.layers.prisma).toBeDefined();
252
+ expect(result.functions.auth.layers).toEqual([{ Ref: 'PrismaLambdaLayer' }]);
253
+ expect(result.functions.auth.package.exclude).toEqual(
254
+ expect.arrayContaining([
255
+ 'node_modules/@prisma/**',
256
+ 'node_modules/.prisma/**',
257
+ 'node_modules/prisma/**',
258
+ 'node_modules/@friggframework/core/generated/**',
259
+ ])
260
+ );
261
+ expect(result.custom.esbuild.external).toEqual(
262
+ expect.arrayContaining(['@prisma/client', 'prisma'])
263
+ );
264
+ });
265
+
266
+ it('omits Prisma layer and bundles Prisma when disabled', () => {
267
+ const result = createBaseDefinition({}, {}, {}, false);
268
+
269
+ expect(result.layers).toEqual({});
270
+ expect(result.functions.auth.layers).toBeUndefined();
271
+ expect(result.functions.auth.package.exclude).not.toEqual(
272
+ expect.arrayContaining([
273
+ 'node_modules/@prisma/**',
274
+ 'node_modules/.prisma/**',
275
+ 'node_modules/prisma/**',
276
+ 'node_modules/@friggframework/core/generated/**',
277
+ ])
278
+ );
279
+ expect(result.custom.esbuild.external).not.toEqual(
280
+ expect.arrayContaining(['@prisma/client', 'prisma'])
281
+ );
282
+ });
283
+
284
+ it('defaults to usePrismaLayer=true when omitted', () => {
285
+ const result = createBaseDefinition({}, {}, {});
286
+ expect(result.functions.auth.layers).toEqual([{ Ref: 'PrismaLambdaLayer' }]);
287
+ });
288
+ });
246
289
  });
247
290
  });
248
291
 
@@ -32,8 +32,15 @@ const { validateAndCleanPlugins, validatePackagingConfiguration } = require('./d
32
32
  const composeServerlessDefinition = async (AppDefinition) => {
33
33
  console.log('🏗️ Composing serverless definition with domain builders...');
34
34
 
35
- // Ensure Prisma layer exists (minimal, runtime only)
36
- await ensurePrismaLayerExists(AppDefinition.database || {});
35
+ // Determine if deployment should use Prisma Lambda Layer (default: true)
36
+ const usePrismaLayer = AppDefinition.usePrismaLambdaLayer !== false;
37
+
38
+ // Ensure Prisma layer exists only when configured to use it
39
+ if (usePrismaLayer) {
40
+ await ensurePrismaLayerExists(AppDefinition.database || {});
41
+ } else {
42
+ console.log('📦 Skipping Prisma Lambda Layer (usePrismaLambdaLayer=false - bundling Prisma with functions)');
43
+ }
37
44
 
38
45
  // Create orchestrator with all domain builders
39
46
  const orchestrator = new BuilderOrchestrator([
@@ -55,7 +62,8 @@ const composeServerlessDefinition = async (AppDefinition) => {
55
62
  const definition = createBaseDefinition(
56
63
  AppDefinition,
57
64
  appEnvironmentVars,
58
- discoveredResources
65
+ discoveredResources,
66
+ usePrismaLayer
59
67
  );
60
68
 
61
69
  // Merge builder results into definition
@@ -44,7 +44,10 @@ function findCorePackage(startDir) {
44
44
  const root = path.parse(currentDir).root;
45
45
 
46
46
  while (currentDir !== root) {
47
- const candidatePath = path.join(currentDir, 'node_modules/@friggframework/core');
47
+ const candidatePath = path.join(
48
+ currentDir,
49
+ 'node_modules/@friggframework/core'
50
+ );
48
51
  if (fs.existsSync(candidatePath)) {
49
52
  return candidatePath;
50
53
  }
@@ -53,7 +56,7 @@ function findCorePackage(startDir) {
53
56
 
54
57
  throw new Error(
55
58
  '@friggframework/core not found in node_modules.\n' +
56
- 'Run "npm install" to install dependencies.'
59
+ 'Run "npm install" to install dependencies.'
57
60
  );
58
61
  }
59
62
 
@@ -66,7 +69,8 @@ function getGeneratedClientPackages(databaseConfig = {}) {
66
69
  const packages = [];
67
70
 
68
71
  // Check if MongoDB is enabled (via mongoDB or documentDB config)
69
- const mongoEnabled = databaseConfig?.mongoDB?.enable === true ||
72
+ const mongoEnabled =
73
+ databaseConfig?.mongoDB?.enable === true ||
70
74
  databaseConfig?.documentDB?.enable === true;
71
75
  if (mongoEnabled) {
72
76
  packages.push('generated/prisma-mongodb');
@@ -90,15 +94,19 @@ function getGeneratedClientPackages(databaseConfig = {}) {
90
94
  }
91
95
 
92
96
  function getMigrationsPackages(clientPackages) {
93
- return clientPackages.map(pkg => ({
97
+ return clientPackages.map((pkg) => ({
94
98
  dbType: pkg.replace('generated/prisma-', ''),
95
- clientPackage: pkg
99
+ clientPackage: pkg,
96
100
  }));
97
101
  }
98
102
 
99
103
  function getMigrationSourcePath(searchPaths, dbType) {
100
104
  for (const searchPath of searchPaths) {
101
- const candidatePath = path.join(searchPath, `prisma-${dbType}`, 'migrations');
105
+ const candidatePath = path.join(
106
+ searchPath,
107
+ `prisma-${dbType}`,
108
+ 'migrations'
109
+ );
102
110
  if (fs.existsSync(candidatePath)) {
103
111
  return candidatePath;
104
112
  }
@@ -110,6 +118,38 @@ function getMigrationDestinationPath(layerNodeModules, clientPackage) {
110
118
  return path.join(layerNodeModules, clientPackage, 'migrations');
111
119
  }
112
120
 
121
+ /**
122
+ * Build list of required file paths for layer verification
123
+ * Different databases have different deployment patterns:
124
+ * - PostgreSQL: Uses migrations (prisma migrate) - requires migration_lock.toml
125
+ * - MongoDB: Uses schema push (prisma db push) - NO migrations directory
126
+ *
127
+ * @param {Array} clientPackages - Generated client packages to include (e.g., ['generated/prisma-postgresql'])
128
+ * @returns {Array} List of required file paths that must exist in the layer
129
+ */
130
+ function getRequiredLayerPaths(clientPackages) {
131
+ const requiredPaths = [
132
+ // Base Prisma client runtime files (always required)
133
+ '@prisma/client/runtime',
134
+ '@prisma/client/index.d.ts',
135
+ ];
136
+
137
+ // Add schema.prisma for each database client
138
+ for (const pkg of clientPackages) {
139
+ requiredPaths.push(`${pkg}/schema.prisma`);
140
+ }
141
+
142
+ // Add migrations ONLY for PostgreSQL (MongoDB uses schema push, no migrations)
143
+ for (const pkg of clientPackages) {
144
+ const dbType = pkg.replace('generated/prisma-', '');
145
+ if (dbType === 'postgresql') {
146
+ requiredPaths.push(`${pkg}/migrations/migration_lock.toml`);
147
+ }
148
+ }
149
+
150
+ return requiredPaths;
151
+ }
152
+
113
153
  // Configuration
114
154
  // Script runs from integration project root (e.g., backend/)
115
155
  // and reads Prisma packages from @friggframework/core
@@ -129,16 +169,16 @@ const BINARY_PATTERNS_TO_REMOVE = [
129
169
 
130
170
  // Files to remove for size optimization
131
171
  const FILES_TO_REMOVE = [
132
- '*.map', // Source maps (37MB savings)
133
- '*.md', // Markdown files
134
- 'LICENSE*', // License files
135
- 'CHANGELOG*', // Changelog files
136
- '*.test.js', // Test files
137
- '*.spec.js', // Spec files
138
- '*mysql.wasm*', // MySQL WASM files (not needed for PostgreSQL)
139
- '*cockroachdb.wasm*', // CockroachDB WASM files
140
- '*sqlite.wasm*', // SQLite WASM files
141
- '*sqlserver.wasm*', // SQL Server WASM files
172
+ '*.map', // Source maps (37MB savings)
173
+ '*.md', // Markdown files
174
+ 'LICENSE*', // License files
175
+ 'CHANGELOG*', // Changelog files
176
+ '*.test.js', // Test files
177
+ '*.spec.js', // Spec files
178
+ '*mysql.wasm*', // MySQL WASM files (not needed for PostgreSQL)
179
+ '*cockroachdb.wasm*', // CockroachDB WASM files
180
+ '*sqlite.wasm*', // SQLite WASM files
181
+ '*sqlserver.wasm*', // SQL Server WASM files
142
182
  ];
143
183
 
144
184
  // ANSI color codes for output
@@ -211,7 +251,7 @@ async function createLayerStructure() {
211
251
 
212
252
  /**
213
253
  * Install Prisma client directly into layer (RUNTIME ONLY - NO CLI)
214
- *
254
+ *
215
255
  * The Prisma CLI is NOT included in this layer to keep it small.
216
256
  * For migrations, the dbMigrate function has its own separate packaging with CLI.
217
257
  */
@@ -269,12 +309,15 @@ async function copyPrismaPackages(clientPackages) {
269
309
  // 2. Project root node_modules (if hoisted from project)
270
310
  // 3. Workspace root node_modules (where core is located - handles hoisting)
271
311
  // 4. Core package itself (for generated/ directories)
272
- const workspaceNodeModules = path.join(path.dirname(CORE_PACKAGE_PATH), '..');
312
+ const workspaceNodeModules = path.join(
313
+ path.dirname(CORE_PACKAGE_PATH),
314
+ '..'
315
+ );
273
316
  const searchPaths = [
274
- path.join(CORE_PACKAGE_PATH, 'node_modules'), // Core's own node_modules
275
- path.join(PROJECT_ROOT, 'node_modules'), // Project node_modules
276
- workspaceNodeModules, // Workspace root node_modules
277
- CORE_PACKAGE_PATH, // Core package itself (for generated/)
317
+ path.join(CORE_PACKAGE_PATH, 'node_modules'), // Core's own node_modules
318
+ path.join(PROJECT_ROOT, 'node_modules'), // Project node_modules
319
+ workspaceNodeModules, // Workspace root node_modules
320
+ CORE_PACKAGE_PATH, // Core package itself (for generated/)
278
321
  ];
279
322
 
280
323
  let copiedCount = 0;
@@ -295,17 +338,19 @@ async function copyPrismaPackages(clientPackages) {
295
338
  if (sourcePath) {
296
339
  const destPath = path.join(LAYER_NODE_MODULES, pkg);
297
340
  await fs.copy(sourcePath, destPath, {
298
- dereference: true, // Follow symlinks
341
+ dereference: true, // Follow symlinks
299
342
  filter: (src) => {
300
343
  // Skip node_modules within Prisma packages
301
344
  return !src.includes('/node_modules/node_modules/');
302
- }
345
+ },
303
346
  });
304
- const fromLocation = sourcePath.includes('@friggframework/core/generated')
347
+ const fromLocation = sourcePath.includes(
348
+ '@friggframework/core/generated'
349
+ )
305
350
  ? 'core package (generated)'
306
351
  : sourcePath.includes('@friggframework/core/node_modules')
307
- ? 'core node_modules'
308
- : 'workspace';
352
+ ? 'core node_modules'
353
+ : 'workspace';
309
354
  logSuccess(`Copied ${pkg} (from ${fromLocation})`);
310
355
  copiedCount++;
311
356
  } else {
@@ -316,18 +361,25 @@ async function copyPrismaPackages(clientPackages) {
316
361
 
317
362
  if (missingPackages.length > 0) {
318
363
  throw new Error(
319
- `Missing generated Prisma clients: ${missingPackages.join(', ')}.\n` +
320
- 'Ensure @friggframework/core has generated Prisma clients (run "npm run prisma:generate" in core package).'
364
+ `Missing generated Prisma clients: ${missingPackages.join(
365
+ ', '
366
+ )}.\n` +
367
+ 'Ensure @friggframework/core has generated Prisma clients (run "npm run prisma:generate" in core package).'
321
368
  );
322
369
  }
323
370
 
324
- logSuccess(`Copied ${copiedCount} generated client packages from @friggframework/core`);
371
+ logSuccess(
372
+ `Copied ${copiedCount} generated client packages from @friggframework/core`
373
+ );
325
374
  }
326
375
 
327
376
  async function copyMigrations(clientPackages) {
328
377
  logStep(5, 'Copying migrations from @friggframework/core');
329
378
 
330
- const workspaceNodeModules = path.join(path.dirname(CORE_PACKAGE_PATH), '..');
379
+ const workspaceNodeModules = path.join(
380
+ path.dirname(CORE_PACKAGE_PATH),
381
+ '..'
382
+ );
331
383
  const searchPaths = [
332
384
  path.join(CORE_PACKAGE_PATH, 'node_modules'),
333
385
  path.join(PROJECT_ROOT, 'node_modules'),
@@ -342,21 +394,34 @@ async function copyMigrations(clientPackages) {
342
394
  const sourcePath = getMigrationSourcePath(searchPaths, dbType);
343
395
 
344
396
  if (sourcePath) {
345
- const destPath = getMigrationDestinationPath(LAYER_NODE_MODULES, clientPackage);
397
+ const destPath = getMigrationDestinationPath(
398
+ LAYER_NODE_MODULES,
399
+ clientPackage
400
+ );
346
401
  await fs.copy(sourcePath, destPath, { dereference: true });
347
-
348
- const fromLocation = sourcePath.includes('@friggframework/core/prisma')
402
+
403
+ const fromLocation = sourcePath.includes(
404
+ '@friggframework/core/prisma'
405
+ )
349
406
  ? 'core package'
350
407
  : 'workspace';
351
- logSuccess(`Copied migrations for ${dbType} (from ${fromLocation})`);
408
+ logSuccess(
409
+ `Copied migrations for ${dbType} (from ${fromLocation})`
410
+ );
352
411
  copiedCount++;
353
412
  } else {
354
- logWarning(`Migrations not found for ${dbType} - this may cause migration failures`);
413
+ logWarning(
414
+ `Migrations not found for ${dbType} - this may cause migration failures`
415
+ );
355
416
  }
356
417
  }
357
418
 
358
419
  if (copiedCount > 0) {
359
- logSuccess(`Copied migrations for ${copiedCount} database ${copiedCount === 1 ? 'type' : 'types'}`);
420
+ logSuccess(
421
+ `Copied migrations for ${copiedCount} database ${
422
+ copiedCount === 1 ? 'type' : 'types'
423
+ }`
424
+ );
360
425
  }
361
426
  }
362
427
 
@@ -375,7 +440,7 @@ async function removeUnnecessaryFiles() {
375
440
  const findCmd = `find "${LAYER_NODE_MODULES}" -name "${pattern}" -type f 2>/dev/null || true`;
376
441
  const files = execSync(findCmd, { encoding: 'utf8' })
377
442
  .split('\n')
378
- .filter(f => f.trim());
443
+ .filter((f) => f.trim());
379
444
 
380
445
  for (const file of files) {
381
446
  if (await fs.pathExists(file)) {
@@ -392,7 +457,9 @@ async function removeUnnecessaryFiles() {
392
457
 
393
458
  if (removedCount > 0) {
394
459
  const sizeMB = (totalSize / (1024 * 1024)).toFixed(2);
395
- logSuccess(`Removed ${removedCount} unnecessary files (saved ${sizeMB} MB)`);
460
+ logSuccess(
461
+ `Removed ${removedCount} unnecessary files (saved ${sizeMB} MB)`
462
+ );
396
463
  } else {
397
464
  log('No unnecessary files found to remove');
398
465
  }
@@ -413,7 +480,7 @@ async function removeNonRhelBinaries() {
413
480
  const findCmd = `find "${LAYER_NODE_MODULES}" -name "${pattern}" 2>/dev/null || true`;
414
481
  const files = execSync(findCmd, { encoding: 'utf8' })
415
482
  .split('\n')
416
- .filter(f => f.trim());
483
+ .filter((f) => f.trim());
417
484
 
418
485
  for (const file of files) {
419
486
  if (await fs.pathExists(file)) {
@@ -430,7 +497,9 @@ async function removeNonRhelBinaries() {
430
497
 
431
498
  if (removedCount > 0) {
432
499
  const sizeMB = (totalSize / (1024 * 1024)).toFixed(2);
433
- logSuccess(`Removed ${removedCount} non-rhel binaries (saved ${sizeMB} MB)`);
500
+ logSuccess(
501
+ `Removed ${removedCount} non-rhel binaries (saved ${sizeMB} MB)`
502
+ );
434
503
  } else {
435
504
  log('No non-rhel binaries found to remove');
436
505
  }
@@ -447,20 +516,24 @@ async function verifyRhelBinaries(expectedClients) {
447
516
  const findCmd = `find "${LAYER_NODE_MODULES}" -name "*rhel-openssl-3.0.x*" 2>/dev/null || true`;
448
517
  const rhelFiles = execSync(findCmd, { encoding: 'utf8' })
449
518
  .split('\n')
450
- .filter(f => f.trim());
519
+ .filter((f) => f.trim());
451
520
 
452
521
  if (rhelFiles.length === 0) {
453
522
  throw new Error(
454
523
  'No rhel-openssl-3.0.x binaries found!\n' +
455
- 'Check that Prisma schemas have binaryTargets = ["native", "rhel-openssl-3.0.x"]'
524
+ 'Check that Prisma schemas have binaryTargets = ["native", "rhel-openssl-3.0.x"]'
456
525
  );
457
526
  }
458
527
 
459
528
  const expectedCount = expectedClients.length;
460
- logSuccess(`Found ${rhelFiles.length} rhel-openssl-3.0.x ${rhelFiles.length === 1 ? 'binary' : 'binaries'} (expected ${expectedCount})`);
529
+ logSuccess(
530
+ `Found ${rhelFiles.length} rhel-openssl-3.0.x ${
531
+ rhelFiles.length === 1 ? 'binary' : 'binaries'
532
+ } (expected ${expectedCount})`
533
+ );
461
534
 
462
535
  // Show the binaries found
463
- rhelFiles.forEach(file => {
536
+ rhelFiles.forEach((file) => {
464
537
  const relativePath = path.relative(LAYER_NODE_MODULES, file);
465
538
  log(` - ${relativePath}`, 'reset');
466
539
  });
@@ -477,26 +550,11 @@ async function verifyRhelBinaries(expectedClients) {
477
550
  async function verifyLayerStructure(clientPackages) {
478
551
  logStep(9, 'Verifying layer structure (runtime only)');
479
552
 
480
- const requiredPaths = [
481
- '@prisma/client/runtime',
482
- '@prisma/client/index.d.ts',
483
- ];
484
-
485
- // Add schema.prisma for each included client
486
- for (const pkg of clientPackages) {
487
- requiredPaths.push(`${pkg}/schema.prisma`);
488
- }
489
-
490
- // Add migrations directory for each included client
491
- for (const pkg of clientPackages) {
492
- requiredPaths.push(`${pkg}/migrations/migration_lock.toml`);
493
- }
553
+ // Get required paths based on database types
554
+ const requiredPaths = getRequiredLayerPaths(clientPackages);
494
555
 
495
556
  // Verify CLI is NOT present (keeps layer small)
496
- const forbiddenPaths = [
497
- 'prisma/build',
498
- '.bin/prisma',
499
- ];
557
+ const forbiddenPaths = ['prisma/build', '.bin/prisma'];
500
558
 
501
559
  let allPresent = true;
502
560
 
@@ -511,7 +569,9 @@ async function verifyLayerStructure(clientPackages) {
511
569
  }
512
570
 
513
571
  if (!allPresent) {
514
- throw new Error('Layer structure verification failed - missing required files');
572
+ throw new Error(
573
+ 'Layer structure verification failed - missing required files'
574
+ );
515
575
  }
516
576
 
517
577
  logSuccess('All required runtime files present');
@@ -520,7 +580,9 @@ async function verifyLayerStructure(clientPackages) {
520
580
  for (const forbiddenPath of forbiddenPaths) {
521
581
  const fullPath = path.join(LAYER_NODE_MODULES, forbiddenPath);
522
582
  if (await fs.pathExists(fullPath)) {
523
- logWarning(` ⚠ ${forbiddenPath} found (should be excluded for minimal layer)`);
583
+ logWarning(
584
+ ` ⚠ ${forbiddenPath} found (should be excluded for minimal layer)`
585
+ );
524
586
  }
525
587
  }
526
588
  }
@@ -548,9 +610,15 @@ async function displayLayerSummary() {
548
610
  log(' - @prisma/engines (minimal - only rhel binary)', 'yellow');
549
611
 
550
612
  log('\nNext steps:', 'bright');
551
- log(' 1. Verify layer structure: ls -lah layers/prisma/nodejs/node_modules/', 'reset');
613
+ log(
614
+ ' 1. Verify layer structure: ls -lah layers/prisma/nodejs/node_modules/',
615
+ 'reset'
616
+ );
552
617
  log(' 2. Deploy to AWS: frigg deploy --stage dev', 'reset');
553
- log(' 3. Check function sizes in Lambda console (should be ~45-55MB)', 'reset');
618
+ log(
619
+ ' 3. Check function sizes in Lambda console (should be ~45-55MB)',
620
+ 'reset'
621
+ );
554
622
 
555
623
  log('\nSee LAMBDA-LAYER-PRISMA.md for complete documentation.', 'yellow');
556
624
  log('='.repeat(60) + '\n', 'bright');
@@ -578,12 +646,12 @@ async function buildPrismaLayer(databaseConfig = {}) {
578
646
  try {
579
647
  await cleanLayerDirectory();
580
648
  await createLayerStructure();
581
- await installPrismaPackages(); // Install runtime client only (NO CLI)
582
- await copyPrismaPackages(clientPackages); // Copy generated clients from core
583
- await copyMigrations(clientPackages); // Copy migrations from core
584
- await removeUnnecessaryFiles(); // Remove source maps, docs, tests (37MB+)
585
- await removeNonRhelBinaries(); // Remove non-Linux binaries
586
- await verifyRhelBinaries(clientPackages); // Verify query engines present
649
+ await installPrismaPackages(); // Install runtime client only (NO CLI)
650
+ await copyPrismaPackages(clientPackages); // Copy generated clients from core
651
+ await copyMigrations(clientPackages); // Copy migrations from core
652
+ await removeUnnecessaryFiles(); // Remove source maps, docs, tests (37MB+)
653
+ await removeNonRhelBinaries(); // Remove non-Linux binaries
654
+ await verifyRhelBinaries(clientPackages); // Verify query engines present
587
655
  await verifyLayerStructure(clientPackages); // Verify minimal runtime structure
588
656
  await displayLayerSummary();
589
657
 
@@ -600,9 +668,15 @@ async function buildPrismaLayer(databaseConfig = {}) {
600
668
  }
601
669
 
602
670
  log('\nTroubleshooting:', 'yellow');
603
- log(' 1. Ensure @friggframework/core has dependencies installed', 'reset');
671
+ log(
672
+ ' 1. Ensure @friggframework/core has dependencies installed',
673
+ 'reset'
674
+ );
604
675
  log(' 2. Run "npm run prisma:generate" in core package', 'reset');
605
- log(' 3. Check that Prisma schemas have correct binaryTargets', 'reset');
676
+ log(
677
+ ' 3. Check that Prisma schemas have correct binaryTargets',
678
+ 'reset'
679
+ );
606
680
  log(' 4. See LAMBDA-LAYER-PRISMA.md for details\n', 'reset');
607
681
 
608
682
  // Throw error instead of exit when used as module
@@ -617,10 +691,11 @@ if (require.main === module) {
617
691
  .catch(() => process.exit(1));
618
692
  }
619
693
 
620
- module.exports = {
621
- buildPrismaLayer,
694
+ module.exports = {
695
+ buildPrismaLayer,
622
696
  getGeneratedClientPackages,
623
697
  getMigrationsPackages,
624
698
  getMigrationSourcePath,
625
- getMigrationDestinationPath
699
+ getMigrationDestinationPath,
700
+ getRequiredLayerPaths,
626
701
  };