@friggframework/devtools 2.0.0--canary.454.25d396a.0 → 2.0.0--canary.461.84ff4f5.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.
@@ -932,6 +932,8 @@ class AWSDiscovery {
932
932
  result.engine = cluster.engine;
933
933
  result.engineVersion = cluster.engineVersion;
934
934
  result.status = cluster.status;
935
+ result.masterUsername = cluster.masterUsername;
936
+ result.isFriggManaged = cluster.isFriggManaged;
935
937
 
936
938
  console.log(`\n✅ Found Aurora Cluster: ${cluster.identifier}`);
937
939
  console.log(` Endpoint: ${cluster.endpoint}:${cluster.port}`);
@@ -982,7 +984,8 @@ class AWSDiscovery {
982
984
  databaseName: cluster.DatabaseName,
983
985
  vpcSecurityGroups: (cluster.VpcSecurityGroups || []).map(sg => sg.VpcSecurityGroupId),
984
986
  dbSubnetGroup: cluster.DBSubnetGroup,
985
- arn: cluster.DBClusterArn
987
+ arn: cluster.DBClusterArn,
988
+ isFriggManaged: this._isFriggManaged(cluster.TagList || [])
986
989
  };
987
990
  }
988
991
 
@@ -3,18 +3,24 @@
3
3
  /**
4
4
  * Build Prisma Lambda Layer
5
5
  *
6
- * Creates a Lambda Layer containing all Prisma packages and rhel-openssl-3.0.x binaries.
6
+ * Creates a Lambda Layer containing Prisma packages and rhel-openssl-3.0.x binaries.
7
7
  * This reduces individual Lambda function sizes by ~60% (120MB → 45MB).
8
8
  *
9
+ * The layer is configured based on AppDefinition database settings:
10
+ * - PostgreSQL: Includes PostgreSQL client + query engine
11
+ * - MongoDB: Includes MongoDB client + query engine (if needed)
12
+ * - Defaults to PostgreSQL only if not specified
13
+ *
9
14
  * Usage:
10
- * node scripts/build-prisma-layer.js
15
+ * node scripts/build-prisma-layer.js [--mongodb] [--postgresql]
11
16
  * npm run build:prisma-layer
12
17
  *
13
18
  * Output:
14
19
  * layers/prisma/nodejs/node_modules/
15
20
  * ├── @prisma/client
16
- * ├── @prisma-mongodb/client
17
- * ├── @prisma-postgresql/client
21
+ * ├── @prisma/engines
22
+ * ├── generated/prisma-postgresql (if PostgreSQL enabled)
23
+ * ├── generated/prisma-mongodb (if MongoDB enabled)
18
24
  * └── prisma (CLI for migrations)
19
25
  *
20
26
  * See: LAMBDA-LAYER-PRISMA.md for complete documentation
@@ -46,6 +52,37 @@ function findCorePackage(startDir) {
46
52
  );
47
53
  }
48
54
 
55
+ /**
56
+ * Determine which database clients to include based on configuration
57
+ * @param {Object} databaseConfig - Database configuration from AppDefinition
58
+ * @returns {Array} List of generated client packages to include
59
+ */
60
+ function getGeneratedClientPackages(databaseConfig = {}) {
61
+ const packages = [];
62
+
63
+ // Check if MongoDB is enabled
64
+ const mongoEnabled = databaseConfig?.mongodb?.enable === true;
65
+ if (mongoEnabled) {
66
+ packages.push('generated/prisma-mongodb');
67
+ log('Including MongoDB client (based on AppDefinition)', 'blue');
68
+ }
69
+
70
+ // Check if PostgreSQL is enabled (default to true if not specified)
71
+ const postgresEnabled = databaseConfig?.postgres?.enable !== false;
72
+ if (postgresEnabled) {
73
+ packages.push('generated/prisma-postgresql');
74
+ log('Including PostgreSQL client (based on AppDefinition)', 'blue');
75
+ }
76
+
77
+ // If neither specified, default to PostgreSQL only
78
+ if (packages.length === 0) {
79
+ packages.push('generated/prisma-postgresql');
80
+ log('No database specified - defaulting to PostgreSQL', 'yellow');
81
+ }
82
+
83
+ return packages;
84
+ }
85
+
49
86
  // Configuration
50
87
  // Script runs from integration project root (e.g., backend/)
51
88
  // and reads Prisma packages from @friggframework/core
@@ -54,14 +91,6 @@ const CORE_PACKAGE_PATH = findCorePackage(PROJECT_ROOT);
54
91
  const LAYER_OUTPUT_PATH = path.join(PROJECT_ROOT, 'layers/prisma');
55
92
  const LAYER_NODE_MODULES = path.join(LAYER_OUTPUT_PATH, 'nodejs/node_modules');
56
93
 
57
- // Packages to include in the layer
58
- const PRISMA_PACKAGES = [
59
- '@prisma/client', // From project node_modules
60
- 'generated/prisma-mongodb', // From @friggframework/core package
61
- 'generated/prisma-postgresql', // From @friggframework/core package
62
- 'prisma', // CLI from project node_modules
63
- ];
64
-
65
94
  // Binary patterns to remove (non-rhel)
66
95
  const BINARY_PATTERNS_TO_REMOVE = [
67
96
  '*darwin*',
@@ -71,6 +100,16 @@ const BINARY_PATTERNS_TO_REMOVE = [
71
100
  '*windows*',
72
101
  ];
73
102
 
103
+ // Files to remove for size optimization
104
+ const FILES_TO_REMOVE = [
105
+ '*.map', // Source maps (37MB savings)
106
+ '*.md', // Markdown files
107
+ 'LICENSE*', // License files
108
+ 'CHANGELOG*', // Changelog files
109
+ '*.test.js', // Test files
110
+ '*.spec.js', // Spec files
111
+ ];
112
+
74
113
  // ANSI color codes for output
75
114
  const colors = {
76
115
  reset: '\x1b[0m',
@@ -140,12 +179,58 @@ async function createLayerStructure() {
140
179
  }
141
180
 
142
181
  /**
143
- * Copy Prisma packages from core to layer
182
+ * Install Prisma CLI and client directly into layer
183
+ * @param {Boolean} includeCLI - Whether to include Prisma CLI (needed for migrations)
144
184
  */
145
- async function copyPrismaPackages() {
146
- logStep(3, 'Copying Prisma packages from @friggframework/core');
185
+ async function installPrismaPackages(includeCLI = false) {
186
+ logStep(3, `Installing Prisma packages (${includeCLI ? 'with CLI' : 'runtime only'})`);
187
+
188
+ // Create a minimal package.json in the layer
189
+ const dependencies = {
190
+ '@prisma/client': '^6.16.3',
191
+ '@prisma/engines': '^6.16.3',
192
+ };
193
+
194
+ // Only include CLI if needed (saves 50MB + 32MB effect dependency)
195
+ if (includeCLI) {
196
+ dependencies['prisma'] = '^6.16.3';
197
+ log(' Including Prisma CLI for migrations', 'yellow');
198
+ } else {
199
+ log(' Runtime only - CLI excluded (saves ~82MB)', 'green');
200
+ }
201
+
202
+ const layerPackageJson = {
203
+ name: 'prisma-lambda-layer',
204
+ version: '1.0.0',
205
+ private: true,
206
+ dependencies,
207
+ };
147
208
 
148
- // Build search paths, handling workspace hoisting
209
+ const packageJsonPath = path.join(LAYER_OUTPUT_PATH, 'nodejs/package.json');
210
+ await fs.writeJson(packageJsonPath, layerPackageJson, { spaces: 2 });
211
+ logSuccess('Created layer package.json');
212
+
213
+ // Install Prisma packages
214
+ log('Installing prisma and @prisma/client...');
215
+ try {
216
+ execSync('npm install --production --no-package-lock', {
217
+ cwd: path.join(LAYER_OUTPUT_PATH, 'nodejs'),
218
+ stdio: 'inherit'
219
+ });
220
+ logSuccess('Prisma packages installed');
221
+ } catch (error) {
222
+ throw new Error(`Failed to install Prisma packages: ${error.message}`);
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Copy generated Prisma clients from @friggframework/core to layer
228
+ * @param {Array} clientPackages - List of generated client packages to copy
229
+ */
230
+ async function copyPrismaPackages(clientPackages) {
231
+ logStep(4, 'Copying generated Prisma clients from @friggframework/core');
232
+
233
+ // Copy the generated clients from core based on database config
149
234
  // Packages can be in:
150
235
  // 1. Core's own node_modules (if not hoisted)
151
236
  // 2. Project root node_modules (if hoisted from project)
@@ -162,7 +247,7 @@ async function copyPrismaPackages() {
162
247
  let copiedCount = 0;
163
248
  let missingPackages = [];
164
249
 
165
- for (const pkg of PRISMA_PACKAGES) {
250
+ for (const pkg of clientPackages) {
166
251
  let sourcePath = null;
167
252
 
168
253
  // Try to find package in search paths
@@ -186,8 +271,8 @@ async function copyPrismaPackages() {
186
271
  const fromLocation = sourcePath.includes('@friggframework/core/generated')
187
272
  ? 'core package (generated)'
188
273
  : sourcePath.includes('@friggframework/core/node_modules')
189
- ? 'core node_modules'
190
- : 'workspace';
274
+ ? 'core node_modules'
275
+ : 'workspace';
191
276
  logSuccess(`Copied ${pkg} (from ${fromLocation})`);
192
277
  copiedCount++;
193
278
  } else {
@@ -198,19 +283,57 @@ async function copyPrismaPackages() {
198
283
 
199
284
  if (missingPackages.length > 0) {
200
285
  throw new Error(
201
- `Missing Prisma packages: ${missingPackages.join(', ')}.\n` +
202
- 'Ensure @friggframework/core is installed with "npm install" and Prisma clients are generated.'
286
+ `Missing generated Prisma clients: ${missingPackages.join(', ')}.\n` +
287
+ 'Ensure @friggframework/core has generated Prisma clients (run "npm run prisma:generate" in core package).'
203
288
  );
204
289
  }
205
290
 
206
- log(`\nCopied ${copiedCount}/${PRISMA_PACKAGES.length} packages`);
291
+ logSuccess(`Copied ${copiedCount} generated client packages from @friggframework/core`);
292
+ }
293
+
294
+ /**
295
+ * Remove unnecessary files to reduce layer size
296
+ */
297
+ async function removeUnnecessaryFiles() {
298
+ logStep(5, 'Removing unnecessary files (source maps, docs, tests)');
299
+
300
+ let removedCount = 0;
301
+ let totalSize = 0;
302
+
303
+ for (const pattern of FILES_TO_REMOVE) {
304
+ try {
305
+ // Find files matching the pattern
306
+ const findCmd = `find "${LAYER_NODE_MODULES}" -name "${pattern}" -type f 2>/dev/null || true`;
307
+ const files = execSync(findCmd, { encoding: 'utf8' })
308
+ .split('\n')
309
+ .filter(f => f.trim());
310
+
311
+ for (const file of files) {
312
+ if (await fs.pathExists(file)) {
313
+ const stats = await fs.stat(file);
314
+ totalSize += stats.size;
315
+ await fs.remove(file);
316
+ removedCount++;
317
+ }
318
+ }
319
+ } catch (error) {
320
+ logWarning(`Error removing ${pattern}: ${error.message}`);
321
+ }
322
+ }
323
+
324
+ if (removedCount > 0) {
325
+ const sizeMB = (totalSize / (1024 * 1024)).toFixed(2);
326
+ logSuccess(`Removed ${removedCount} unnecessary files (saved ${sizeMB} MB)`);
327
+ } else {
328
+ log('No unnecessary files found to remove');
329
+ }
207
330
  }
208
331
 
209
332
  /**
210
333
  * Remove non-rhel engine binaries to reduce layer size
211
334
  */
212
335
  async function removeNonRhelBinaries() {
213
- logStep(4, 'Removing non-rhel engine binaries');
336
+ logStep(6, 'Removing non-rhel engine binaries');
214
337
 
215
338
  let removedCount = 0;
216
339
  let totalSize = 0;
@@ -246,9 +369,10 @@ async function removeNonRhelBinaries() {
246
369
 
247
370
  /**
248
371
  * Verify rhel binaries are present
372
+ * @param {Array} expectedClients - List of client packages that should have binaries
249
373
  */
250
- async function verifyRhelBinaries() {
251
- logStep(5, 'Verifying rhel-openssl-3.0.x binaries are present');
374
+ async function verifyRhelBinaries(expectedClients) {
375
+ logStep(7, 'Verifying rhel-openssl-3.0.x binaries are present');
252
376
 
253
377
  try {
254
378
  const findCmd = `find "${LAYER_NODE_MODULES}" -name "*rhel-openssl-3.0.x*" 2>/dev/null || true`;
@@ -263,7 +387,8 @@ async function verifyRhelBinaries() {
263
387
  );
264
388
  }
265
389
 
266
- logSuccess(`Found ${rhelFiles.length} rhel-openssl-3.0.x binaries`);
390
+ const expectedCount = expectedClients.length;
391
+ logSuccess(`Found ${rhelFiles.length} rhel-openssl-3.0.x ${rhelFiles.length === 1 ? 'binary' : 'binaries'} (expected ${expectedCount})`);
267
392
 
268
393
  // Show the binaries found
269
394
  rhelFiles.forEach(file => {
@@ -277,18 +402,24 @@ async function verifyRhelBinaries() {
277
402
 
278
403
  /**
279
404
  * Verify required files exist
405
+ * @param {Boolean} includeCLI - Whether CLI files should be present
280
406
  */
281
- async function verifyLayerStructure() {
282
- logStep(6, 'Verifying layer structure');
407
+ async function verifyLayerStructure(includeCLI) {
408
+ logStep(8, 'Verifying layer structure');
283
409
 
284
410
  const requiredPaths = [
285
411
  '@prisma/client/runtime',
286
412
  '@prisma/client/index.d.ts',
287
- 'generated/prisma-mongodb/schema.prisma',
288
- 'generated/prisma-postgresql/schema.prisma',
289
- 'prisma/build',
413
+ '@prisma/engines',
414
+ 'generated/prisma-postgresql/schema.prisma', // PostgreSQL
290
415
  ];
291
416
 
417
+ // Only check for CLI files if CLI was included
418
+ if (includeCLI) {
419
+ requiredPaths.push('prisma/build');
420
+ requiredPaths.push('.bin/prisma');
421
+ }
422
+
292
423
  let allPresent = true;
293
424
 
294
425
  for (const requiredPath of requiredPaths) {
@@ -312,7 +443,7 @@ async function verifyLayerStructure() {
312
443
  * Calculate and display final layer size
313
444
  */
314
445
  async function displayLayerSummary() {
315
- logStep(7, 'Layer build summary');
446
+ logStep(9, 'Layer build summary');
316
447
 
317
448
  const layerSizeMB = getDirectorySize(LAYER_OUTPUT_PATH);
318
449
 
@@ -324,9 +455,11 @@ async function displayLayerSummary() {
324
455
  log(`Layer size: ~${layerSizeMB} MB`, 'green');
325
456
 
326
457
  log('\nPackages included:', 'bright');
327
- PRISMA_PACKAGES.forEach(pkg => {
328
- log(` - ${pkg}`, 'reset');
329
- });
458
+ log(' - prisma (CLI - installed)', 'reset');
459
+ log(' - @prisma/client (runtime - installed)', 'reset');
460
+ log(' - @prisma/engines (binaries - installed)', 'reset');
461
+ log(' - generated/prisma-postgresql (PostgreSQL only)', 'reset');
462
+ log('\nNote: MongoDB support excluded (not used in this project)', 'yellow');
330
463
 
331
464
  log('\nNext steps:', 'bright');
332
465
  log(' 1. Verify layer structure: ls -lah layers/prisma/nodejs/node_modules/', 'reset');
@@ -339,12 +472,14 @@ async function displayLayerSummary() {
339
472
 
340
473
  /**
341
474
  * Main build function
475
+ * @param {Object} databaseConfig - Database configuration from AppDefinition.database
476
+ * @param {Boolean} includeCLI - Whether to include Prisma CLI (false = runtime only)
342
477
  */
343
- async function buildPrismaLayer() {
478
+ async function buildPrismaLayer(databaseConfig = {}, includeCLI = false) {
344
479
  const startTime = Date.now();
345
480
 
346
481
  log('\n' + '='.repeat(60), 'bright');
347
- log(' Building Prisma Lambda Layer', 'bright');
482
+ log(` Building Prisma Lambda Layer ${includeCLI ? '(with CLI)' : '(runtime only)'}`, 'bright');
348
483
  log('='.repeat(60) + '\n', 'bright');
349
484
 
350
485
  // Log paths
@@ -352,13 +487,18 @@ async function buildPrismaLayer() {
352
487
  log(`Core package: ${CORE_PACKAGE_PATH}`, 'reset');
353
488
  log(`Layer output: ${LAYER_OUTPUT_PATH}\n`, 'reset');
354
489
 
490
+ // Determine which database clients to include
491
+ const clientPackages = getGeneratedClientPackages(databaseConfig);
492
+
355
493
  try {
356
494
  await cleanLayerDirectory();
357
495
  await createLayerStructure();
358
- await copyPrismaPackages();
359
- await removeNonRhelBinaries();
360
- await verifyRhelBinaries();
361
- await verifyLayerStructure();
496
+ await installPrismaPackages(includeCLI); // Install Prisma packages (CLI optional)
497
+ await copyPrismaPackages(clientPackages); // Copy generated clients from core
498
+ await removeUnnecessaryFiles(); // Remove source maps, docs, tests (37MB+)
499
+ await removeNonRhelBinaries(); // Remove non-Linux binaries
500
+ await verifyRhelBinaries(clientPackages); // Verify query engines present
501
+ await verifyLayerStructure(includeCLI); // Verify structure (conditional on CLI)
362
502
  await displayLayerSummary();
363
503
 
364
504
  const duration = ((Date.now() - startTime) / 1000).toFixed(2);
@@ -554,6 +554,7 @@ const gatherDiscoveredResources = async (AppDefinition) => {
554
554
  vpc: AppDefinition.vpc || {},
555
555
  encryption: AppDefinition.encryption || {},
556
556
  ssm: AppDefinition.ssm || {},
557
+ database: AppDefinition.database || {},
557
558
  serviceName: AppDefinition.name || 'create-frigg-app',
558
559
  stage: stage,
559
560
  };
@@ -637,26 +638,66 @@ const createBaseDefinition = (
637
638
  ) => {
638
639
  const region = process.env.AWS_REGION || 'us-east-1';
639
640
 
641
+ // Function-level package config to exclude Prisma and AWS SDK
642
+ // Uses native Serverless package.exclude since jetpack function-level config isn't supported in v3
643
+ const functionPackageConfig = {
644
+ exclude: [
645
+ // Exclude AWS SDK (already in Lambda runtime)
646
+ 'node_modules/aws-sdk/**',
647
+ 'node_modules/@aws-sdk/**',
648
+
649
+ // Exclude Prisma (provided via Lambda Layer)
650
+ 'node_modules/@prisma/**',
651
+ 'node_modules/.prisma/**',
652
+ 'node_modules/prisma/**',
653
+ 'node_modules/@friggframework/core/generated/**',
654
+ ],
655
+ };
656
+
640
657
  return {
641
658
  frameworkVersion: '>=3.17.0',
642
659
  service: AppDefinition.name || 'create-frigg-app',
643
660
  package: {
644
661
  individually: true,
662
+ // NOTE: These patterns are NOT used when serverless-jetpack is enabled with trace mode
663
+ // Jetpack's trace mode completely overrides package.patterns during dependency resolution
664
+ // These are kept commented out as a fallback if Jetpack needs to be disabled
645
665
  patterns: [
646
- // Existing AWS SDK exclusions
647
- '!**/node_modules/aws-sdk/**',
648
- '!**/node_modules/@aws-sdk/**',
649
- '!package.json',
650
-
651
- // GLOBAL Prisma exclusions - all Prisma packages moved to Lambda Layer
652
- // This reduces each function from ~120MB to ~45MB (60% reduction)
653
- '!node_modules/@prisma/**',
654
- '!node_modules/.prisma/**',
655
- '!node_modules/@prisma-mongodb/**',
656
- '!node_modules/@prisma-postgresql/**',
657
- '!node_modules/prisma/**',
658
- // Prisma packages will be provided at runtime via Lambda Layer
659
- // See: LAMBDA-LAYER-PRISMA.md for complete documentation
666
+ // AWS SDK exclusions (already in Lambda runtime)
667
+ // '!**/node_modules/aws-sdk/**',
668
+ // '!**/node_modules/@aws-sdk/**',
669
+
670
+ // Prisma exclusions (provided via Lambda Layer)
671
+ // '!**/node_modules/@prisma/**',
672
+ // '!**/node_modules/.prisma/**',
673
+ // '!**/node_modules/@prisma-mongodb/**',
674
+ // '!**/node_modules/@prisma-postgresql/**',
675
+ // '!**/node_modules/prisma/**',
676
+
677
+ // Exclude Prisma generated clients from @friggframework/core
678
+ // '!**/node_modules/@friggframework/core/generated/**',
679
+
680
+ // Exclude development and test files
681
+ // '!**/test/**',
682
+ // '!**/tests/**',
683
+ // '!**/*.test.js',
684
+ // '!**/*.spec.js',
685
+ // '!**/*.map',
686
+ // '!**/jest.config.js',
687
+ // '!**/jest.unit.config.js',
688
+ // '!**/.eslintrc.json',
689
+ // '!**/.prettierrc',
690
+ // '!**/.prettierignore',
691
+ // '!**/.markdownlintignore',
692
+ // '!**/docker-compose.yml',
693
+ // '!**/package.json',
694
+ // '!**/README.md',
695
+ // '!**/*.md',
696
+
697
+ // Exclude .DS_Store and other OS files
698
+ // '!**/.DS_Store',
699
+ // '!**/.git/**',
700
+ // '!**/.claude-flow/**',
660
701
  ],
661
702
  },
662
703
  useDotenv: true,
@@ -711,8 +752,7 @@ const createBaseDefinition = (
711
752
  },
712
753
  },
713
754
  plugins: [
714
- // Temporarily disabled Jetpack - it ignores package.patterns in dependency mode
715
- // 'serverless-jetpack',
755
+ 'serverless-jetpack',
716
756
  'serverless-dotenv-plugin',
717
757
  'serverless-offline-sqs',
718
758
  'serverless-offline',
@@ -733,15 +773,18 @@ const createBaseDefinition = (
733
773
  secretAccessKey: 'root',
734
774
  skipCacheInvalidation: false,
735
775
  },
736
- // Jetpack config removed - testing with standard Serverless packaging
737
- // jetpack: {
738
- // base: '..',
739
- // },
776
+ jetpack: {
777
+ base: '..', // Essential for reaching handlers in node_modules/@friggframework
778
+ // NOTE: Service-level preInclude applies to EVERYTHING (functions + layers)
779
+ // We need to ONLY exclude from functions, not from the Prisma layer
780
+ // Solution: Apply exclusions at function level instead
781
+ },
740
782
  },
741
783
  functions: {
742
784
  auth: {
743
785
  handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
744
786
  layers: [{ Ref: 'PrismaLambdaLayer' }],
787
+ package: functionPackageConfig,
745
788
  events: [
746
789
  { httpApi: { path: '/api/integrations', method: 'ANY' } },
747
790
  {
@@ -756,11 +799,13 @@ const createBaseDefinition = (
756
799
  user: {
757
800
  handler: 'node_modules/@friggframework/core/handlers/routers/user.handler',
758
801
  layers: [{ Ref: 'PrismaLambdaLayer' }],
802
+ package: functionPackageConfig,
759
803
  events: [{ httpApi: { path: '/user/{proxy+}', method: 'ANY' } }],
760
804
  },
761
805
  health: {
762
806
  handler: 'node_modules/@friggframework/core/handlers/routers/health.handler',
763
807
  layers: [{ Ref: 'PrismaLambdaLayer' }],
808
+ package: functionPackageConfig,
764
809
  events: [
765
810
  { httpApi: { path: '/health', method: 'GET' } },
766
811
  { httpApi: { path: '/health/{proxy+}', method: 'GET' } },
@@ -768,11 +813,13 @@ const createBaseDefinition = (
768
813
  },
769
814
  dbMigrate: {
770
815
  handler: 'node_modules/@friggframework/core/handlers/workers/db-migration.handler',
816
+ // Uses Prisma Layer (includes CLI) - simpler than standalone packaging
771
817
  layers: [{ Ref: 'PrismaLambdaLayer' }],
772
818
  timeout: 300, // 5 minutes for long-running migrations
773
819
  memorySize: 512, // Extra memory for Prisma CLI operations
774
820
  reservedConcurrency: 1, // Prevent concurrent migrations
775
- description: 'Runs database migrations via Prisma (invoke manually from CI/CD)',
821
+ description: 'Runs database migrations via Prisma (invoke manually from CI/CD). Uses Prisma layer with CLI.',
822
+ package: functionPackageConfig, // Use same exclusions as other functions
776
823
  // No events - this function is invoked manually via AWS CLI
777
824
  maximumEventAge: 60, // Don't retry old migration requests (60 seconds)
778
825
  maximumRetryAttempts: 0, // Don't auto-retry failed migrations
@@ -786,22 +833,13 @@ const createBaseDefinition = (
786
833
  PRISMA_HIDE_UPDATE_MESSAGE: '1', // Suppress update messages
787
834
  PRISMA_MIGRATE_SKIP_SEED: '1', // Skip seeding during migrations
788
835
  },
789
- // Function-specific packaging: Include Prisma schemas (CLI from layer)
790
- package: {
791
- patterns: [
792
- // Include Prisma schemas from @friggframework/core
793
- // Note: Prisma CLI and clients come from Lambda Layer
794
- 'node_modules/@friggframework/core/prisma-mongodb/**',
795
- 'node_modules/@friggframework/core/prisma-postgresql/**',
796
- ],
797
- },
798
836
  },
799
837
  },
800
838
  layers: {
801
839
  prisma: {
802
840
  path: 'layers/prisma',
803
841
  name: '${self:service}-prisma-${sls:stage}',
804
- description: 'Prisma ORM clients for MongoDB and PostgreSQL with rhel-openssl-3.0.x binaries. Reduces function sizes by ~60% (120MB → 45MB). See LAMBDA-LAYER-PRISMA.md for details.',
842
+ description: 'Prisma ORM client with CLI and rhel-openssl-3.0.x binaries. Configured based on AppDefinition database settings. Used by all functions.',
805
843
  compatibleRuntimes: ['nodejs18.x', 'nodejs20.x'],
806
844
  retain: false, // Don't retain old layer versions
807
845
  },
@@ -2158,8 +2196,13 @@ const createAuroraInfrastructure = (definition, AppDefinition, discoveredResourc
2158
2196
 
2159
2197
  const useExistingAurora = (definition, AppDefinition, discoveredResources) => {
2160
2198
  const dbConfig = AppDefinition.database.postgres;
2199
+ const selfHeal = AppDefinition.database?.postgres?.selfHeal !== false; // Default to true
2161
2200
 
2162
2201
  console.log(`🔗 Using existing Aurora cluster: ${discoveredResources.aurora.clusterIdentifier}`);
2202
+ console.log(`[DEBUG] discoveredResources.aurora.isFriggManaged: ${discoveredResources.aurora.isFriggManaged}`);
2203
+ console.log(`[DEBUG] selfHeal: ${selfHeal}`);
2204
+ console.log(`[DEBUG] discoveredResources.aurora.secretArn: ${discoveredResources.aurora.secretArn}`);
2205
+ console.log(`[DEBUG] dbConfig.secretArn: ${dbConfig.secretArn}`);
2163
2206
 
2164
2207
  // Add IAM permissions for Secrets Manager if secret exists
2165
2208
  if (discoveredResources.aurora.secretArn) {
@@ -2208,8 +2251,84 @@ const useExistingAurora = (definition, AppDefinition, discoveredResources) => {
2208
2251
  }
2209
2252
  ]
2210
2253
  };
2254
+ } else if (selfHeal && discoveredResources.aurora?.isFriggManaged) {
2255
+ // Self-healing mode: recreate missing secret for Frigg-managed cluster
2256
+ console.log('⚠️ No database secret found for Frigg-managed cluster');
2257
+ console.log('🔧 Self-healing enabled: Creating new database secret with automatic password rotation');
2258
+
2259
+ // Get the current master username from the cluster
2260
+ const currentUsername = discoveredResources.aurora.masterUsername || dbConfig.masterUsername || 'frigg_admin';
2261
+
2262
+ // Create Secrets Manager Secret (database credentials)
2263
+ // Note: We generate a NEW password, which will be synced to the cluster via SecretTargetAttachment
2264
+ definition.resources.Resources.FriggDatabaseSecret = {
2265
+ Type: 'AWS::SecretsManager::Secret',
2266
+ Properties: {
2267
+ Name: '${self:service}-${self:provider.stage}-aurora-credentials',
2268
+ Description: 'Aurora PostgreSQL credentials for Frigg application (auto-healed)',
2269
+ GenerateSecretString: {
2270
+ SecretStringTemplate: JSON.stringify({
2271
+ username: currentUsername
2272
+ }),
2273
+ GenerateStringKey: 'password',
2274
+ PasswordLength: 32,
2275
+ ExcludeCharacters: '"@/\\`\''
2276
+ },
2277
+ Tags: [
2278
+ { Key: 'ManagedBy', Value: 'Frigg' },
2279
+ { Key: 'Service', Value: '${self:service}' },
2280
+ { Key: 'Stage', Value: '${self:provider.stage}' },
2281
+ { Key: 'AutoHealed', Value: 'true' }
2282
+ ]
2283
+ }
2284
+ };
2285
+
2286
+ // Create SecretTargetAttachment to link secret to existing cluster
2287
+ // This will automatically rotate the cluster password to match the secret!
2288
+ definition.resources.Resources.FriggSecretAttachment = {
2289
+ Type: 'AWS::SecretsManager::SecretTargetAttachment',
2290
+ Properties: {
2291
+ SecretId: { Ref: 'FriggDatabaseSecret' },
2292
+ TargetId: discoveredResources.aurora.clusterIdentifier,
2293
+ TargetType: 'AWS::RDS::DBCluster'
2294
+ }
2295
+ };
2296
+
2297
+ // Add IAM permissions for the new secret
2298
+ definition.provider.iamRoleStatements.push({
2299
+ Effect: 'Allow',
2300
+ Action: [
2301
+ 'secretsmanager:GetSecretValue',
2302
+ 'secretsmanager:DescribeSecret'
2303
+ ],
2304
+ Resource: { Ref: 'FriggDatabaseSecret' }
2305
+ });
2306
+
2307
+ // Set DATABASE_URL from new secret
2308
+ definition.provider.environment.DATABASE_URL = {
2309
+ 'Fn::Sub': [
2310
+ 'postgresql://${Username}:${Password}@${Endpoint}:${Port}/${DatabaseName}',
2311
+ {
2312
+ Username: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:username}}' },
2313
+ Password: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:password}}' },
2314
+ Endpoint: discoveredResources.aurora.endpoint,
2315
+ Port: discoveredResources.aurora.port,
2316
+ DatabaseName: dbConfig.databaseName || 'frigg_db'
2317
+ }
2318
+ ]
2319
+ };
2320
+
2321
+ console.log('✅ Self-healing configuration complete:');
2322
+ console.log(' - New secret will be created with auto-generated password');
2323
+ console.log(' - SecretTargetAttachment will automatically update cluster password');
2324
+ console.log(' - No manual password sync required!');
2211
2325
  } else {
2212
- throw new Error('No database secret found. Provide secretArn in database.postgres configuration or ensure Secrets Manager secret exists.');
2326
+ throw new Error(
2327
+ 'No database secret found. Options:\n' +
2328
+ ' 1. Provide secretArn in database.postgres configuration\n' +
2329
+ ' 2. Ensure Secrets Manager secret exists\n' +
2330
+ ' 3. Enable self-healing: set database.postgres.selfHeal to true (for Frigg-managed clusters only)'
2331
+ );
2213
2332
  }
2214
2333
 
2215
2334
  // Set DB_TYPE for Prisma client selection
@@ -2306,6 +2425,18 @@ const attachIntegrations = (definition, AppDefinition) => {
2306
2425
  `Processing ${AppDefinition.integrations.length} integrations...`
2307
2426
  );
2308
2427
 
2428
+ // Get the functionPackageConfig from the definition (defined in createBaseDefinition)
2429
+ const functionPackageConfig = {
2430
+ exclude: [
2431
+ 'node_modules/aws-sdk/**',
2432
+ 'node_modules/@aws-sdk/**',
2433
+ 'node_modules/@prisma/**',
2434
+ 'node_modules/.prisma/**',
2435
+ 'node_modules/prisma/**',
2436
+ 'node_modules/@friggframework/core/generated/**',
2437
+ ],
2438
+ };
2439
+
2309
2440
  for (const integration of AppDefinition.integrations) {
2310
2441
  if (!integration?.Definition?.name) {
2311
2442
  throw new Error('Invalid integration: missing Definition or name');
@@ -2318,6 +2449,7 @@ const attachIntegrations = (definition, AppDefinition) => {
2318
2449
 
2319
2450
  definition.functions[integrationName] = {
2320
2451
  handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
2452
+ package: functionPackageConfig,
2321
2453
  events: [
2322
2454
  {
2323
2455
  httpApi: {
@@ -2346,6 +2478,7 @@ const attachIntegrations = (definition, AppDefinition) => {
2346
2478
  const queueWorkerName = `${integrationName}QueueWorker`;
2347
2479
  definition.functions[queueWorkerName] = {
2348
2480
  handler: `node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.${integrationName}.queueWorker`,
2481
+ package: functionPackageConfig,
2349
2482
  reservedConcurrency: 5,
2350
2483
  events: [
2351
2484
  {
@@ -2412,8 +2545,9 @@ const configureWebsockets = (definition, AppDefinition) => {
2412
2545
  /**
2413
2546
  * Ensure Prisma Lambda Layer exists
2414
2547
  * Automatically builds the layer if it doesn't exist in the project root
2548
+ * @param {Object} databaseConfig - Database configuration from AppDefinition.database
2415
2549
  */
2416
- async function ensurePrismaLayerExists() {
2550
+ async function ensurePrismaLayerExists(databaseConfig = {}) {
2417
2551
  const projectRoot = process.cwd();
2418
2552
  const layerPath = path.join(projectRoot, 'layers/prisma');
2419
2553
 
@@ -2425,10 +2559,12 @@ async function ensurePrismaLayerExists() {
2425
2559
 
2426
2560
  // Layer doesn't exist - build it automatically
2427
2561
  console.log('📦 Prisma Lambda Layer not found - building automatically...');
2562
+ console.log(' Building layer with CLI (used by all functions including dbMigrate)');
2428
2563
  console.log(' This may take a minute on first deployment.\n');
2429
2564
 
2430
2565
  try {
2431
- await buildPrismaLayer();
2566
+ // Build layer WITH CLI (includeCLI = true) - all functions use same layer
2567
+ await buildPrismaLayer(databaseConfig, true);
2432
2568
  console.log('✓ Prisma Lambda Layer built successfully\n');
2433
2569
  } catch (error) {
2434
2570
  console.error('✗ Failed to build Prisma Lambda Layer:', error.message);
@@ -2441,7 +2577,8 @@ const composeServerlessDefinition = async (AppDefinition) => {
2441
2577
  console.log('composeServerlessDefinition', AppDefinition);
2442
2578
 
2443
2579
  // Ensure Prisma layer exists before generating serverless config
2444
- await ensurePrismaLayerExists();
2580
+ // Pass database config so layer only includes needed database clients
2581
+ await ensurePrismaLayerExists(AppDefinition.database || {});
2445
2582
 
2446
2583
  const discoveredResources = await gatherDiscoveredResources(AppDefinition);
2447
2584
  const appEnvironmentVars = getAppEnvironmentVars(AppDefinition);
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.454.25d396a.0",
4
+ "version": "2.0.0--canary.461.84ff4f5.0",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-ec2": "^3.835.0",
7
7
  "@aws-sdk/client-kms": "^3.835.0",
@@ -11,8 +11,8 @@
11
11
  "@babel/eslint-parser": "^7.18.9",
12
12
  "@babel/parser": "^7.25.3",
13
13
  "@babel/traverse": "^7.25.3",
14
- "@friggframework/schemas": "2.0.0--canary.454.25d396a.0",
15
- "@friggframework/test": "2.0.0--canary.454.25d396a.0",
14
+ "@friggframework/schemas": "2.0.0--canary.461.84ff4f5.0",
15
+ "@friggframework/test": "2.0.0--canary.461.84ff4f5.0",
16
16
  "@hapi/boom": "^10.0.1",
17
17
  "@inquirer/prompts": "^5.3.8",
18
18
  "axios": "^1.7.2",
@@ -34,8 +34,8 @@
34
34
  "serverless-http": "^2.7.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@friggframework/eslint-config": "2.0.0--canary.454.25d396a.0",
38
- "@friggframework/prettier-config": "2.0.0--canary.454.25d396a.0",
37
+ "@friggframework/eslint-config": "2.0.0--canary.461.84ff4f5.0",
38
+ "@friggframework/prettier-config": "2.0.0--canary.461.84ff4f5.0",
39
39
  "aws-sdk-client-mock": "^4.1.0",
40
40
  "aws-sdk-client-mock-jest": "^4.1.0",
41
41
  "jest": "^30.1.3",
@@ -70,5 +70,5 @@
70
70
  "publishConfig": {
71
71
  "access": "public"
72
72
  },
73
- "gitHead": "25d396ab0d73094bfab4853f9ff15a5500174ca7"
73
+ "gitHead": "84ff4f5a8ab85a143a491d4337965e534c1a73fb"
74
74
  }