@friggframework/devtools 2.0.0--canary.461.4860820.0 → 2.0.0--canary.461.12ba2eb.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.
@@ -277,7 +277,7 @@ class AuroraBuilder extends InfrastructureBuilder {
277
277
  // Check if we should auto-create credentials
278
278
  if (dbConfig.autoCreateCredentials && !discoveredResources.databaseSecretArn) {
279
279
  console.log(' Creating Secrets Manager secret and rotating Aurora password...');
280
-
280
+
281
281
  // Create Secrets Manager secret with auto-generated password
282
282
  result.resources.FriggDBSecret = {
283
283
  Type: 'AWS::SecretsManager::Secret',
@@ -473,15 +473,15 @@ exports.handler = async (event, context) => {
473
473
  // No secret and no auto-create - set individual DB connection components
474
474
  // The application will construct DATABASE_URL at runtime from these components + DATABASE_USER + DATABASE_PASSWORD
475
475
  const dbName = dbConfig.database || 'frigg';
476
-
476
+
477
477
  result.environment.DATABASE_HOST = discoveredResources.auroraClusterEndpoint;
478
478
  result.environment.DATABASE_PORT = String(discoveredResources.auroraPort || 5432);
479
479
  result.environment.DATABASE_NAME = dbName;
480
-
480
+
481
481
  // Note: DATABASE_URL is NOT set here to avoid Serverless variable resolution errors
482
482
  // The application (Frigg Core) should construct it at runtime from:
483
483
  // DATABASE_HOST, DATABASE_PORT, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD
484
-
484
+
485
485
  console.log(' ℹ️ No Secrets Manager secret found - set DATABASE_USER and DATABASE_PASSWORD in Lambda environment');
486
486
  console.log(' ℹ️ Application will construct DATABASE_URL at runtime from DATABASE_HOST, DATABASE_PORT, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD');
487
487
  console.log(' ℹ️ Or enable autoCreateCredentials=true to automatically create and rotate credentials');
@@ -508,14 +508,47 @@ exports.handler = async (event, context) => {
508
508
 
509
509
  /**
510
510
  * Build DATABASE_URL connection string
511
+ * @param {string|object} host - Database host (string or CloudFormation intrinsic function)
512
+ * @param {string|number|object} port - Database port (string/number or CloudFormation intrinsic function)
513
+ * @param {string} database - Database name
514
+ * @param {string|object} secretRef - Secret ARN (string) or CloudFormation Ref object
511
515
  */
512
516
  buildDatabaseUrl(host, port, database, secretRef) {
517
+ // Handle secretRef as either a string ARN or CloudFormation Ref object
518
+ const resolveSecretRef = (secretRefValue) => {
519
+ if (typeof secretRefValue === 'object' && secretRefValue.Ref) {
520
+ // CloudFormation Ref - use nested Fn::Sub to resolve it
521
+ return {
522
+ 'Fn::Sub': [
523
+ '{{resolve:secretsmanager:${SecretArn}:SecretString:username}}',
524
+ { SecretArn: secretRefValue },
525
+ ],
526
+ };
527
+ }
528
+ // String ARN - use directly
529
+ return `{{resolve:secretsmanager:${secretRefValue}:SecretString:username}}`;
530
+ };
531
+
532
+ const resolveSecretPassword = (secretRefValue) => {
533
+ if (typeof secretRefValue === 'object' && secretRefValue.Ref) {
534
+ // CloudFormation Ref - use nested Fn::Sub to resolve it
535
+ return {
536
+ 'Fn::Sub': [
537
+ '{{resolve:secretsmanager:${SecretArn}:SecretString:password}}',
538
+ { SecretArn: secretRefValue },
539
+ ],
540
+ };
541
+ }
542
+ // String ARN - use directly
543
+ return `{{resolve:secretsmanager:${secretRefValue}:SecretString:password}}`;
544
+ };
545
+
513
546
  return {
514
547
  'Fn::Sub': [
515
548
  `postgresql://\${Username}:\${Password}@\${Host}:\${Port}/\${Database}`,
516
549
  {
517
- Username: `{{resolve:secretsmanager:${secretRef}:SecretString:username}}`,
518
- Password: `{{resolve:secretsmanager:${secretRef}:SecretString:password}}`,
550
+ Username: resolveSecretRef(secretRef),
551
+ Password: resolveSecretPassword(secretRef),
519
552
  Host: host,
520
553
  Port: port,
521
554
  Database: database,
@@ -388,8 +388,14 @@ describe('AuroraBuilder', () => {
388
388
  // Check DATABASE_URL uses the secret
389
389
  expect(result.environment.DATABASE_URL).toBeDefined();
390
390
  expect(result.environment.DATABASE_URL['Fn::Sub']).toBeDefined();
391
- expect(result.environment.DATABASE_URL['Fn::Sub'][1].Username).toContain('resolve:secretsmanager');
392
- expect(result.environment.DATABASE_URL['Fn::Sub'][1].Password).toContain('resolve:secretsmanager');
391
+
392
+ // Username and Password should use nested Fn::Sub to resolve the Ref
393
+ expect(result.environment.DATABASE_URL['Fn::Sub'][1].Username['Fn::Sub']).toBeDefined();
394
+ expect(result.environment.DATABASE_URL['Fn::Sub'][1].Password['Fn::Sub']).toBeDefined();
395
+
396
+ // Should contain secretsmanager resolution
397
+ expect(result.environment.DATABASE_URL['Fn::Sub'][1].Username['Fn::Sub'][0]).toContain('resolve:secretsmanager');
398
+ expect(result.environment.DATABASE_URL['Fn::Sub'][1].Password['Fn::Sub'][0]).toContain('resolve:secretsmanager');
393
399
 
394
400
  // Check IAM permissions for secret access
395
401
  const secretPermission = result.iamStatements.find(stmt =>
@@ -426,11 +432,11 @@ describe('AuroraBuilder', () => {
426
432
  expect(result.environment.DATABASE_HOST).toBe('cluster.abc.us-east-1.rds.amazonaws.com');
427
433
  expect(result.environment.DATABASE_PORT).toBe('5432');
428
434
  expect(result.environment.DATABASE_NAME).toBe('frigg');
429
-
435
+
430
436
  // DATABASE_URL should NOT be set (to avoid Serverless variable resolution errors)
431
437
  // The application should construct it at runtime from DATABASE_HOST, DATABASE_PORT, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD
432
438
  expect(result.environment.DATABASE_URL).toBeUndefined();
433
-
439
+
434
440
  // DATABASE_USER and DATABASE_PASSWORD should come from appDefinition.environment
435
441
  // and will be set by the environment-builder, not here
436
442
  });
@@ -523,15 +529,49 @@ describe('AuroraBuilder', () => {
523
529
  const result = await auroraBuilder.build(appDefinition, discoveredResources);
524
530
 
525
531
  const zipFileCode = result.resources.PasswordRotatorLambda.Properties.Code.ZipFile;
526
-
532
+
527
533
  // Should not contain template literals that would conflict with CloudFormation ${} substitution
528
534
  // CloudFormation uses ${} for parameter substitution, so we should avoid `${variable}` in ZipFile
529
535
  expect(zipFileCode).not.toMatch(/`.*\$\{(?!env:).*\}`/); // No template literals with ${} except ${env:...}
530
-
536
+
531
537
  // Should use string concatenation instead
532
538
  expect(zipFileCode).toContain("'Successfully rotated password for cluster: ' + ClusterIdentifier");
533
539
  });
534
540
 
541
+ it('should properly handle Ref objects in buildDatabaseUrl when autoCreateCredentials is enabled', async () => {
542
+ const appDefinition = {
543
+ database: {
544
+ postgres: {
545
+ enable: true,
546
+ management: 'discover',
547
+ autoCreateCredentials: true,
548
+ database: 'testdb',
549
+ },
550
+ },
551
+ };
552
+
553
+ const discoveredResources = {
554
+ auroraClusterEndpoint: 'cluster.abc.us-east-1.rds.amazonaws.com',
555
+ auroraPort: 5432,
556
+ };
557
+
558
+ const result = await auroraBuilder.build(appDefinition, discoveredResources);
559
+
560
+ const dbUrl = result.environment.DATABASE_URL;
561
+
562
+ // Should use Fn::Sub with nested Fn::Sub to resolve the Ref
563
+ expect(dbUrl['Fn::Sub']).toBeDefined();
564
+ expect(dbUrl['Fn::Sub'][0]).toBe('postgresql://${Username}:${Password}@${Host}:${Port}/${Database}');
565
+
566
+ // The Username and Password should use Fn::Sub to resolve the secret Ref, not literal "[object Object]"
567
+ expect(dbUrl['Fn::Sub'][1].Username['Fn::Sub']).toBeDefined();
568
+ expect(dbUrl['Fn::Sub'][1].Password['Fn::Sub']).toBeDefined();
569
+
570
+ // Should not contain the literal string "[object Object]"
571
+ const jsonOutput = JSON.stringify(dbUrl);
572
+ expect(jsonOutput).not.toContain('[object Object]');
573
+ });
574
+
535
575
  it('should properly escape ExcludeCharacters for valid JSON in CloudFormation template', async () => {
536
576
  const appDefinition = {
537
577
  database: {
@@ -551,15 +591,15 @@ describe('AuroraBuilder', () => {
551
591
  const result = await auroraBuilder.build(appDefinition, discoveredResources);
552
592
 
553
593
  const excludeChars = result.resources.FriggDBSecret.Properties.GenerateSecretString.ExcludeCharacters;
554
-
594
+
555
595
  // Should properly escape the backslash so it's valid JSON
556
596
  // In JavaScript string: '"@/\\' represents the string: "@/\
557
597
  // When serialized to JSON, backslash must be doubled: '"@/\\'
558
598
  expect(excludeChars).toBe('"@/\\\\');
559
-
599
+
560
600
  // Verify it can be JSON-stringified without errors
561
601
  expect(() => JSON.stringify(result.resources.FriggDBSecret)).not.toThrow();
562
-
602
+
563
603
  // Verify the JSON output has the correct escape sequence
564
604
  const jsonOutput = JSON.stringify(result.resources.FriggDBSecret);
565
605
  expect(jsonOutput).toContain('\\"@/\\\\\\\\'); // In JSON string: "\"@/\\\\"
@@ -72,6 +72,61 @@ class MigrationBuilder extends InfrastructureBuilder {
72
72
 
73
73
  console.log(' ✓ Created DbMigrationQueue resource');
74
74
 
75
+ // Package configuration for migration functions (reuse from base-definition-factory)
76
+ const migrationPackageConfig = {
77
+ individually: true,
78
+ exclude: [
79
+ // Exclude ALL nested node_modules
80
+ 'node_modules/**/node_modules/**',
81
+ 'node_modules/aws-sdk/**',
82
+ 'node_modules/@aws-sdk/**',
83
+ 'node_modules/esbuild/**',
84
+ 'node_modules/@esbuild/**',
85
+ 'node_modules/typescript/**',
86
+ 'node_modules/webpack/**',
87
+ 'node_modules/osls/**',
88
+ 'node_modules/serverless-esbuild/**',
89
+ 'node_modules/serverless-jetpack/**',
90
+ 'node_modules/serverless-offline/**',
91
+ 'node_modules/serverless-offline-sqs/**',
92
+ 'node_modules/serverless-dotenv-plugin/**',
93
+ 'node_modules/serverless-kms-grants/**',
94
+ 'node_modules/@friggframework/test/**',
95
+ 'node_modules/@friggframework/eslint-config/**',
96
+ 'node_modules/@friggframework/prettier-config/**',
97
+ 'node_modules/@friggframework/devtools/**',
98
+ 'node_modules/@friggframework/serverless-plugin/**',
99
+ 'node_modules/jest/**',
100
+ 'node_modules/prettier/**',
101
+ 'node_modules/eslint/**',
102
+ 'node_modules/@friggframework/core/generated/prisma-mongodb/**',
103
+ 'node_modules/@friggframework/core/integrations/**',
104
+ 'node_modules/@friggframework/core/user/**',
105
+ 'node_modules/@friggframework/core/handlers/routers/**',
106
+ '**/query-engine-darwin*',
107
+ '**/schema-engine-darwin*',
108
+ '**/libquery_engine-darwin*',
109
+ '**/*-darwin-arm64*',
110
+ '**/*-darwin*',
111
+ '**/runtime/*.wasm',
112
+ '**/*.wasm*',
113
+ 'src/**',
114
+ 'test/**',
115
+ 'layers/**',
116
+ 'coverage/**',
117
+ 'deploy.log',
118
+ '.env.backup',
119
+ 'docker-compose.yml',
120
+ 'jest.config.js',
121
+ 'jest.unit.config.js',
122
+ 'package-lock.json',
123
+ '**/*.test.js',
124
+ '**/*.spec.js',
125
+ '**/.claude-flow/**',
126
+ '**/.swarm/**',
127
+ ],
128
+ };
129
+
75
130
  // Create migration worker Lambda (triggered by SQS)
76
131
  result.functions.dbMigrationWorker = {
77
132
  handler: 'node_modules/@friggframework/core/handlers/workers/db-migration.handler',
@@ -81,6 +136,7 @@ class MigrationBuilder extends InfrastructureBuilder {
81
136
  memorySize: 1024, // Extra memory for Prisma operations
82
137
  reservedConcurrency: 1, // Process one migration at a time (critical for safety)
83
138
  description: 'Database migration worker (triggered by SQS queue)',
139
+ package: migrationPackageConfig,
84
140
  events: [
85
141
  {
86
142
  sqs: {
@@ -101,6 +157,7 @@ class MigrationBuilder extends InfrastructureBuilder {
101
157
  timeout: 30, // Router just queues jobs, doesn't run migrations
102
158
  memorySize: 512,
103
159
  description: 'Database migration HTTP API (POST to trigger, GET to check status)',
160
+ package: migrationPackageConfig,
104
161
  events: [
105
162
  { httpApi: { path: '/db-migrate', method: 'POST' } },
106
163
  { httpApi: { path: '/db-migrate/{processId}', method: 'GET' } },
@@ -172,6 +172,44 @@ describe('MigrationBuilder', () => {
172
172
  });
173
173
  });
174
174
 
175
+ it('should configure package exclusions for migration functions to reduce Lambda size', async () => {
176
+ const appDef = {
177
+ database: {
178
+ postgres: {
179
+ enable: true,
180
+ },
181
+ },
182
+ };
183
+
184
+ const result = await builder.build(appDef, {});
185
+
186
+ // Both migration functions should have package configs
187
+ expect(result.functions.dbMigrationWorker.package).toBeDefined();
188
+ expect(result.functions.dbMigrationRouter.package).toBeDefined();
189
+
190
+ // Check worker package config
191
+ const workerPackage = result.functions.dbMigrationWorker.package;
192
+ expect(workerPackage.individually).toBe(true);
193
+ expect(workerPackage.exclude).toBeDefined();
194
+ expect(Array.isArray(workerPackage.exclude)).toBe(true);
195
+
196
+ // Verify critical exclusions for size optimization
197
+ expect(workerPackage.exclude).toContain('test/**');
198
+ expect(workerPackage.exclude).toContain('**/*.test.js');
199
+ expect(workerPackage.exclude).toContain('node_modules/**/node_modules/**');
200
+ expect(workerPackage.exclude).toContain('node_modules/esbuild/**');
201
+ expect(workerPackage.exclude).toContain('node_modules/typescript/**');
202
+ expect(workerPackage.exclude).toContain('node_modules/@friggframework/devtools/**');
203
+ expect(workerPackage.exclude).toContain('src/**'); // Migration handlers don't need backend source
204
+
205
+ // Check router package config
206
+ const routerPackage = result.functions.dbMigrationRouter.package;
207
+ expect(routerPackage.individually).toBe(true);
208
+ expect(routerPackage.exclude).toBeDefined();
209
+ expect(routerPackage.exclude).toContain('test/**');
210
+ expect(routerPackage.exclude).toContain('node_modules/**/node_modules/**');
211
+ });
212
+
175
213
  it('should add queue URL to environment', async () => {
176
214
  const appDef = {
177
215
  database: {
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.461.4860820.0",
4
+ "version": "2.0.0--canary.461.12ba2eb.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.461.4860820.0",
15
- "@friggframework/test": "2.0.0--canary.461.4860820.0",
14
+ "@friggframework/schemas": "2.0.0--canary.461.12ba2eb.0",
15
+ "@friggframework/test": "2.0.0--canary.461.12ba2eb.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.461.4860820.0",
38
- "@friggframework/prettier-config": "2.0.0--canary.461.4860820.0",
37
+ "@friggframework/eslint-config": "2.0.0--canary.461.12ba2eb.0",
38
+ "@friggframework/prettier-config": "2.0.0--canary.461.12ba2eb.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": "486082018ba9bc219bf266048eb4c846bd9a45fb"
73
+ "gitHead": "12ba2eb2888f4999e52cf4b8f6eadc9f1beacfb0"
74
74
  }