@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
|
|
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
|
|
17
|
-
* ├──
|
|
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
|
-
*
|
|
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
|
|
146
|
-
logStep(3,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
190
|
-
|
|
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
|
|
202
|
-
'Ensure @friggframework/core
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
'
|
|
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(
|
|
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
|
-
|
|
328
|
-
|
|
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(
|
|
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
|
|
359
|
-
await
|
|
360
|
-
await
|
|
361
|
-
await
|
|
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
|
-
//
|
|
647
|
-
'!**/node_modules/aws-sdk/**',
|
|
648
|
-
'!**/node_modules/@aws-sdk/**',
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
//
|
|
652
|
-
//
|
|
653
|
-
'
|
|
654
|
-
'
|
|
655
|
-
'
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
//
|
|
659
|
-
|
|
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
|
-
|
|
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
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
15
|
-
"@friggframework/test": "2.0.0--canary.
|
|
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.
|
|
38
|
-
"@friggframework/prettier-config": "2.0.0--canary.
|
|
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": "
|
|
73
|
+
"gitHead": "84ff4f5a8ab85a143a491d4337965e534c1a73fb"
|
|
74
74
|
}
|