@friggframework/devtools 2.0.0-next.53 → 2.0.0-next.55
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.
- package/frigg-cli/README.md +13 -14
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +267 -166
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +45 -14
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +44 -3
- package/frigg-cli/db-setup-command/index.js +75 -22
- package/frigg-cli/deploy-command/index.js +6 -3
- package/frigg-cli/utils/database-validator.js +18 -5
- package/frigg-cli/utils/error-messages.js +84 -12
- package/infrastructure/README.md +28 -0
- package/infrastructure/domains/database/migration-builder.js +26 -20
- package/infrastructure/domains/database/migration-builder.test.js +27 -0
- package/infrastructure/domains/integration/integration-builder.js +17 -13
- package/infrastructure/domains/integration/integration-builder.test.js +23 -0
- package/infrastructure/domains/networking/vpc-builder.js +240 -18
- package/infrastructure/domains/networking/vpc-builder.test.js +711 -13
- package/infrastructure/domains/networking/vpc-resolver.js +221 -40
- package/infrastructure/domains/networking/vpc-resolver.test.js +318 -18
- package/infrastructure/domains/security/kms-builder.js +55 -6
- package/infrastructure/domains/security/kms-builder.test.js +19 -1
- package/infrastructure/domains/shared/cloudformation-discovery.js +310 -13
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +395 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +41 -6
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +39 -0
- package/infrastructure/domains/shared/resource-discovery.js +17 -5
- package/infrastructure/domains/shared/resource-discovery.test.js +36 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +30 -20
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +43 -0
- package/infrastructure/infrastructure-composer.js +11 -3
- package/infrastructure/scripts/build-prisma-layer.js +153 -78
- package/infrastructure/scripts/build-prisma-layer.test.js +27 -11
- package/layers/prisma/.build-complete +3 -0
- package/package.json +7 -7
package/infrastructure/README.md
CHANGED
|
@@ -257,6 +257,34 @@ aws lambda get-function-configuration \
|
|
|
257
257
|
--query 'Layers[*].Arn'
|
|
258
258
|
```
|
|
259
259
|
|
|
260
|
+
**Disabling Prisma Layer (Bundle with Functions):**
|
|
261
|
+
|
|
262
|
+
By default, Prisma ships via a Lambda Layer. You can disable the layer and bundle Prisma directly with each Lambda:
|
|
263
|
+
|
|
264
|
+
```javascript
|
|
265
|
+
const appDefinition = {
|
|
266
|
+
name: 'my-app',
|
|
267
|
+
usePrismaLambdaLayer: false, // Bundle Prisma with each function
|
|
268
|
+
integrations: [{ Definition: { name: 'asana' } }],
|
|
269
|
+
};
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**When to disable the Prisma Layer:**
|
|
273
|
+
|
|
274
|
+
- ✅ Your CI/CD IAM user lacks `lambda:PublishLayerVersion`
|
|
275
|
+
- ✅ Deploying to an AWS region/account with Lambda layer restrictions
|
|
276
|
+
- ✅ Troubleshooting Prisma client loading issues
|
|
277
|
+
- ✅ You prefer a simpler deployment artifact (no shared layer management)
|
|
278
|
+
|
|
279
|
+
**Trade-offs:**
|
|
280
|
+
|
|
281
|
+
| Mode | Function Size | Deploy Speed | IAM Permissions Required |
|
|
282
|
+
|------|---------------|--------------|-------------------------|
|
|
283
|
+
| **Layer (default)** | ~45MB per function | Faster (layer cached) | `lambda:PublishLayerVersion` |
|
|
284
|
+
| **Bundled** | ~80MB per function | Slower (Prisma uploaded per function) | None (layer-related) |
|
|
285
|
+
|
|
286
|
+
> ℹ️ When `usePrismaLambdaLayer: false`, Prisma stays inside each bundle and the runtime automatically loads the correct binary. No extra configuration is required.
|
|
287
|
+
|
|
260
288
|
## Usage Examples
|
|
261
289
|
|
|
262
290
|
### Basic Deployment
|
|
@@ -57,6 +57,8 @@ class MigrationBuilder extends InfrastructureBuilder {
|
|
|
57
57
|
// Backwards compatibility: Translate old schema to new ownership schema
|
|
58
58
|
appDefinition = this.translateLegacyConfig(appDefinition, discoveredResources);
|
|
59
59
|
|
|
60
|
+
const usePrismaLayer = appDefinition.usePrismaLambdaLayer !== false;
|
|
61
|
+
|
|
60
62
|
const result = {
|
|
61
63
|
functions: {}, // Lambda function definitions
|
|
62
64
|
resources: {},
|
|
@@ -76,7 +78,7 @@ class MigrationBuilder extends InfrastructureBuilder {
|
|
|
76
78
|
console.log(` Queue: ${decisions.queue.ownership} - ${decisions.queue.reason}`);
|
|
77
79
|
|
|
78
80
|
// Build resources based on ownership decisions
|
|
79
|
-
await this.buildFromDecisions(decisions, appDefinition, discoveredResources, result);
|
|
81
|
+
await this.buildFromDecisions(decisions, appDefinition, discoveredResources, result, usePrismaLayer);
|
|
80
82
|
|
|
81
83
|
console.log(`[${this.name}] ✅ Migration infrastructure configuration completed`);
|
|
82
84
|
return result;
|
|
@@ -207,7 +209,7 @@ class MigrationBuilder extends InfrastructureBuilder {
|
|
|
207
209
|
/**
|
|
208
210
|
* Build migration resources based on ownership decisions
|
|
209
211
|
*/
|
|
210
|
-
async buildFromDecisions(decisions, appDefinition, discoveredResources, result) {
|
|
212
|
+
async buildFromDecisions(decisions, appDefinition, discoveredResources, result, usePrismaLayer = true) {
|
|
211
213
|
// Determine if we need to create resources or use existing ones
|
|
212
214
|
const shouldCreateBucket = decisions.bucket.ownership === ResourceOwnership.STACK;
|
|
213
215
|
const shouldCreateQueue = decisions.queue.ownership === ResourceOwnership.STACK;
|
|
@@ -215,16 +217,16 @@ class MigrationBuilder extends InfrastructureBuilder {
|
|
|
215
217
|
if (shouldCreateBucket && shouldCreateQueue && !decisions.bucket.physicalId && !decisions.queue.physicalId) {
|
|
216
218
|
// Create all new migration infrastructure
|
|
217
219
|
console.log(' → Creating new migration infrastructure in stack');
|
|
218
|
-
await this.createMigrationInfrastructure(appDefinition, result);
|
|
220
|
+
await this.createMigrationInfrastructure(appDefinition, result, usePrismaLayer);
|
|
219
221
|
} else if ((decisions.bucket.ownership === ResourceOwnership.STACK && decisions.bucket.physicalId) ||
|
|
220
222
|
(decisions.queue.ownership === ResourceOwnership.STACK && decisions.queue.physicalId)) {
|
|
221
223
|
// Resources exist in stack - add definitions (CloudFormation idempotency)
|
|
222
224
|
console.log(' → Adding migration definitions to template (existing in stack)');
|
|
223
|
-
await this.createMigrationInfrastructure(appDefinition, result);
|
|
225
|
+
await this.createMigrationInfrastructure(appDefinition, result, usePrismaLayer);
|
|
224
226
|
} else {
|
|
225
227
|
// Use external resources
|
|
226
228
|
console.log(' → Using external migration resources');
|
|
227
|
-
await this.useExternalMigrationResources(decisions, appDefinition, result);
|
|
229
|
+
await this.useExternalMigrationResources(decisions, appDefinition, result, usePrismaLayer);
|
|
228
230
|
}
|
|
229
231
|
}
|
|
230
232
|
|
|
@@ -232,17 +234,19 @@ class MigrationBuilder extends InfrastructureBuilder {
|
|
|
232
234
|
* Create Lambda function definitions for database migrations
|
|
233
235
|
* Based on refactor/add-better-support-for-commands branch implementation
|
|
234
236
|
*/
|
|
235
|
-
async createFunctionDefinitions(result) {
|
|
237
|
+
async createFunctionDefinitions(result, usePrismaLayer = true) {
|
|
236
238
|
console.log(' 🔍 DEBUG: createFunctionDefinitions called');
|
|
237
239
|
console.log(' 🔍 DEBUG: result.functions is:', typeof result.functions, result.functions);
|
|
238
240
|
// Migration WORKER package config (needs Prisma CLI WASM files)
|
|
239
241
|
const migrationWorkerPackageConfig = {
|
|
240
242
|
individually: true,
|
|
241
243
|
exclude: [
|
|
242
|
-
// Exclude Prisma runtime client
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
// Exclude Prisma runtime client when using Lambda Layer (but keep CLI folder)
|
|
245
|
+
...(usePrismaLayer ? [
|
|
246
|
+
'node_modules/@prisma/client/**',
|
|
247
|
+
'node_modules/.prisma/**',
|
|
248
|
+
'node_modules/@friggframework/core/generated/**',
|
|
249
|
+
] : []),
|
|
246
250
|
// But KEEP node_modules/prisma/** (the CLI with WASM)
|
|
247
251
|
|
|
248
252
|
// Exclude ALL nested node_modules
|
|
@@ -328,13 +332,15 @@ class MigrationBuilder extends InfrastructureBuilder {
|
|
|
328
332
|
const migrationRouterPackageConfig = {
|
|
329
333
|
individually: true,
|
|
330
334
|
exclude: [
|
|
331
|
-
// Exclude Prisma runtime client
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
+
// Exclude Prisma runtime client when using Lambda Layer
|
|
336
|
+
...(usePrismaLayer ? [
|
|
337
|
+
'node_modules/@prisma/client/**',
|
|
338
|
+
'node_modules/.prisma/**',
|
|
339
|
+
'node_modules/@friggframework/core/generated/**',
|
|
340
|
+
] : []),
|
|
335
341
|
|
|
336
|
-
// Router
|
|
337
|
-
'node_modules/prisma/**',
|
|
342
|
+
// Router only skips Prisma CLI if Lambda Layer is enabled
|
|
343
|
+
...(usePrismaLayer ? ['node_modules/prisma/**'] : []),
|
|
338
344
|
|
|
339
345
|
// Exclude ALL nested node_modules
|
|
340
346
|
'node_modules/**/node_modules/**',
|
|
@@ -422,7 +428,7 @@ class MigrationBuilder extends InfrastructureBuilder {
|
|
|
422
428
|
console.log(' 🔍 DEBUG: About to create dbMigrationWorker...');
|
|
423
429
|
result.functions.dbMigrationWorker = {
|
|
424
430
|
handler: 'node_modules/@friggframework/core/handlers/workers/db-migration.handler',
|
|
425
|
-
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
431
|
+
...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
|
|
426
432
|
skipEsbuild: true,
|
|
427
433
|
timeout: 900, // 15 minutes for long migrations
|
|
428
434
|
memorySize: 1024, // Extra memory for Prisma operations
|
|
@@ -484,12 +490,12 @@ class MigrationBuilder extends InfrastructureBuilder {
|
|
|
484
490
|
* Create migration infrastructure CloudFormation resources
|
|
485
491
|
* Creates S3 bucket, SQS queue, and Lambda function definitions
|
|
486
492
|
*/
|
|
487
|
-
async createMigrationInfrastructure(appDefinition, result) {
|
|
493
|
+
async createMigrationInfrastructure(appDefinition, result, usePrismaLayer = true) {
|
|
488
494
|
console.log(' 🔍 DEBUG: createMigrationInfrastructure called');
|
|
489
495
|
console.log(' 🔍 DEBUG: result object before createFunctionDefinitions:', Object.keys(result));
|
|
490
496
|
|
|
491
497
|
// Create Lambda function definitions first (they reference the queue)
|
|
492
|
-
await this.createFunctionDefinitions(result);
|
|
498
|
+
await this.createFunctionDefinitions(result, usePrismaLayer);
|
|
493
499
|
|
|
494
500
|
console.log(' 🔍 DEBUG: result.functions after createFunctionDefinitions:', Object.keys(result.functions || {}));
|
|
495
501
|
|
|
@@ -614,7 +620,7 @@ class MigrationBuilder extends InfrastructureBuilder {
|
|
|
614
620
|
* Use external migration resources (S3 bucket and SQS queue)
|
|
615
621
|
* Only references external resources - Lambda functions are defined in serverless.yml
|
|
616
622
|
*/
|
|
617
|
-
async useExternalMigrationResources(decisions, appDefinition, result) {
|
|
623
|
+
async useExternalMigrationResources(decisions, appDefinition, result, usePrismaLayer = true) {
|
|
618
624
|
// Reference external bucket
|
|
619
625
|
const bucketName = decisions.bucket.physicalId;
|
|
620
626
|
if (!bucketName) {
|
|
@@ -290,5 +290,32 @@ describe('MigrationBuilder', () => {
|
|
|
290
290
|
expect(builder.getName()).toBe('MigrationBuilder');
|
|
291
291
|
});
|
|
292
292
|
});
|
|
293
|
+
|
|
294
|
+
describe('usePrismaLayer configuration', () => {
|
|
295
|
+
const baseAppDefinition = {
|
|
296
|
+
database: {
|
|
297
|
+
postgres: { enable: true },
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
it('includes Prisma layer by default', async () => {
|
|
302
|
+
const result = await builder.build(baseAppDefinition, {});
|
|
303
|
+
expect(result.functions.dbMigrationWorker.layers).toEqual([{ Ref: 'PrismaLambdaLayer' }]);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('omits Prisma layer when disabled', async () => {
|
|
307
|
+
const appDef = { ...baseAppDefinition, usePrismaLambdaLayer: false };
|
|
308
|
+
const result = await builder.build(appDef, {});
|
|
309
|
+
expect(result.functions.dbMigrationWorker.layers).toBeUndefined();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('bundles Prisma runtime when layer disabled', async () => {
|
|
313
|
+
const appDef = { ...baseAppDefinition, usePrismaLambdaLayer: false };
|
|
314
|
+
const result = await builder.build(appDef, {});
|
|
315
|
+
expect(result.functions.dbMigrationWorker.package.exclude).not.toEqual(
|
|
316
|
+
expect.arrayContaining(['node_modules/@prisma/client/**'])
|
|
317
|
+
);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
293
320
|
});
|
|
294
321
|
|
|
@@ -62,6 +62,8 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
62
62
|
console.log(`\n[${this.name}] Configuring integrations...`);
|
|
63
63
|
console.log(` Processing ${appDefinition.integrations.length} integrations...`);
|
|
64
64
|
|
|
65
|
+
const usePrismaLayer = appDefinition.usePrismaLambdaLayer !== false;
|
|
66
|
+
|
|
65
67
|
const result = {
|
|
66
68
|
functions: {},
|
|
67
69
|
resources: {},
|
|
@@ -87,7 +89,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
87
89
|
});
|
|
88
90
|
|
|
89
91
|
// Build resources based on ownership decisions
|
|
90
|
-
await this.buildFromDecisions(decisions, appDefinition, result);
|
|
92
|
+
await this.buildFromDecisions(decisions, appDefinition, result, usePrismaLayer);
|
|
91
93
|
|
|
92
94
|
console.log(`[${this.name}] ✅ Integration configuration completed`);
|
|
93
95
|
return result;
|
|
@@ -142,7 +144,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
142
144
|
/**
|
|
143
145
|
* Build integration resources based on ownership decisions
|
|
144
146
|
*/
|
|
145
|
-
async buildFromDecisions(decisions, appDefinition, result) {
|
|
147
|
+
async buildFromDecisions(decisions, appDefinition, result, usePrismaLayer = true) {
|
|
146
148
|
// Create InternalErrorQueue if ownership = STACK
|
|
147
149
|
const shouldCreateInternalErrorQueue = decisions.internalErrorQueue.ownership === ResourceOwnership.STACK;
|
|
148
150
|
|
|
@@ -155,7 +157,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
// Create Lambda function definitions and queue resources for each integration
|
|
158
|
-
const functionPackageConfig = this.createFunctionPackageConfig();
|
|
160
|
+
const functionPackageConfig = this.createFunctionPackageConfig(usePrismaLayer);
|
|
159
161
|
|
|
160
162
|
for (const integration of appDefinition.integrations) {
|
|
161
163
|
const integrationName = integration.Definition.name;
|
|
@@ -164,7 +166,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
164
166
|
console.log(`\n Adding integration: ${integrationName}`);
|
|
165
167
|
|
|
166
168
|
// Create Lambda function definitions (serverless template code)
|
|
167
|
-
await this.createFunctionDefinitions(integration, functionPackageConfig, result);
|
|
169
|
+
await this.createFunctionDefinitions(integration, functionPackageConfig, result, usePrismaLayer);
|
|
168
170
|
|
|
169
171
|
// Create or reference SQS queue based on ownership decision
|
|
170
172
|
const shouldCreateQueue = queueDecision.ownership === ResourceOwnership.STACK;
|
|
@@ -182,7 +184,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
182
184
|
/**
|
|
183
185
|
* Create function package exclusion configuration
|
|
184
186
|
*/
|
|
185
|
-
createFunctionPackageConfig() {
|
|
187
|
+
createFunctionPackageConfig(usePrismaLayer = true) {
|
|
186
188
|
return {
|
|
187
189
|
exclude: [
|
|
188
190
|
// Exclude AWS SDK (provided by Lambda runtime)
|
|
@@ -190,10 +192,12 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
190
192
|
'node_modules/@aws-sdk/**',
|
|
191
193
|
|
|
192
194
|
// Exclude Prisma (provided via Lambda Layer)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
...(usePrismaLayer ? [
|
|
196
|
+
'node_modules/@prisma/**',
|
|
197
|
+
'node_modules/.prisma/**',
|
|
198
|
+
'node_modules/prisma/**',
|
|
199
|
+
'node_modules/@friggframework/core/generated/**',
|
|
200
|
+
] : []),
|
|
197
201
|
|
|
198
202
|
// Exclude ALL nested node_modules
|
|
199
203
|
'node_modules/**/node_modules/**',
|
|
@@ -249,7 +253,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
249
253
|
* Create Lambda function definitions for an integration
|
|
250
254
|
* These are serverless framework template function definitions
|
|
251
255
|
*/
|
|
252
|
-
async createFunctionDefinitions(integration, functionPackageConfig, result) {
|
|
256
|
+
async createFunctionDefinitions(integration, functionPackageConfig, result, usePrismaLayer = true) {
|
|
253
257
|
const integrationName = integration.Definition.name;
|
|
254
258
|
|
|
255
259
|
// Add webhook handler if enabled (BEFORE catch-all proxy route)
|
|
@@ -263,7 +267,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
263
267
|
handler: `node_modules/@friggframework/core/handlers/routers/integration-webhook-routers.handlers.${integrationName}Webhook.handler`,
|
|
264
268
|
skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
|
|
265
269
|
package: functionPackageConfig,
|
|
266
|
-
layers: [{ Ref: 'PrismaLambdaLayer' }], // Webhook handlers need Prisma for credential lookups
|
|
270
|
+
...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }), // Webhook handlers need Prisma for credential lookups
|
|
267
271
|
events: [
|
|
268
272
|
{
|
|
269
273
|
httpApi: {
|
|
@@ -287,7 +291,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
287
291
|
handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
|
|
288
292
|
skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
|
|
289
293
|
package: functionPackageConfig,
|
|
290
|
-
layers: [{ Ref: 'PrismaLambdaLayer' }], // HTTP handlers need Prisma for integration queries
|
|
294
|
+
...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }), // HTTP handlers need Prisma for integration queries
|
|
291
295
|
events: [
|
|
292
296
|
{
|
|
293
297
|
httpApi: {
|
|
@@ -305,7 +309,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
305
309
|
handler: `node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.${integrationName}.queueWorker`,
|
|
306
310
|
skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
|
|
307
311
|
package: functionPackageConfig,
|
|
308
|
-
layers: [{ Ref: 'PrismaLambdaLayer' }], // Queue workers need Prisma for database operations
|
|
312
|
+
...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }), // Queue workers need Prisma for database operations
|
|
309
313
|
reservedConcurrency: 5,
|
|
310
314
|
events: [
|
|
311
315
|
{
|
|
@@ -662,6 +662,29 @@ describe('IntegrationBuilder', () => {
|
|
|
662
662
|
{ Ref: 'PrismaLambdaLayer' }
|
|
663
663
|
]);
|
|
664
664
|
});
|
|
665
|
+
|
|
666
|
+
it('should not attach Prisma layer when usePrismaLambdaLayer=false', async () => {
|
|
667
|
+
const appDefinition = {
|
|
668
|
+
usePrismaLambdaLayer: false,
|
|
669
|
+
integrations: [
|
|
670
|
+
{
|
|
671
|
+
Definition: {
|
|
672
|
+
name: 'asana',
|
|
673
|
+
webhooks: true,
|
|
674
|
+
},
|
|
675
|
+
},
|
|
676
|
+
],
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
const result = await integrationBuilder.build(appDefinition, {});
|
|
680
|
+
|
|
681
|
+
expect(result.functions.asana.layers).toBeUndefined();
|
|
682
|
+
expect(result.functions.asanaQueueWorker.layers).toBeUndefined();
|
|
683
|
+
expect(result.functions.asanaWebhook.layers).toBeUndefined();
|
|
684
|
+
expect(result.functions.asana.package.exclude).not.toEqual(
|
|
685
|
+
expect.arrayContaining(['node_modules/@prisma/**'])
|
|
686
|
+
);
|
|
687
|
+
});
|
|
665
688
|
});
|
|
666
689
|
});
|
|
667
690
|
|