@friggframework/devtools 2.0.0--canary.461.ec909cf.0 → 2.0.0--canary.461.9483dbe.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.
Files changed (71) hide show
  1. package/frigg-cli/__tests__/unit/commands/build.test.js +6 -6
  2. package/frigg-cli/build-command/index.js +1 -1
  3. package/frigg-cli/deploy-command/index.js +6 -6
  4. package/frigg-cli/generate-command/index.js +2 -2
  5. package/frigg-cli/generate-iam-command.js +10 -10
  6. package/frigg-cli/start-command/index.js +1 -1
  7. package/frigg-cli/start-command/start-command.test.js +3 -3
  8. package/frigg-cli/utils/database-validator.js +14 -21
  9. package/infrastructure/REFACTOR.md +532 -0
  10. package/infrastructure/TRANSFORMATION-VISUAL.md +239 -0
  11. package/infrastructure/__tests__/postgres-config.test.js +1 -1
  12. package/infrastructure/create-frigg-infrastructure.js +1 -1
  13. package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
  14. package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
  15. package/infrastructure/domains/database/aurora-discovery.js +81 -0
  16. package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
  17. package/infrastructure/domains/integration/integration-builder.js +178 -0
  18. package/infrastructure/domains/integration/integration-builder.test.js +362 -0
  19. package/infrastructure/domains/integration/websocket-builder.js +69 -0
  20. package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
  21. package/infrastructure/domains/networking/vpc-discovery.test.js +257 -0
  22. package/infrastructure/domains/parameters/ssm-builder.js +79 -0
  23. package/infrastructure/domains/parameters/ssm-builder.test.js +188 -0
  24. package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
  25. package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
  26. package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
  27. package/infrastructure/domains/security/kms-builder.js +169 -0
  28. package/infrastructure/domains/security/kms-builder.test.js +354 -0
  29. package/infrastructure/domains/security/kms-discovery.js +80 -0
  30. package/infrastructure/domains/security/kms-discovery.test.js +176 -0
  31. package/infrastructure/domains/shared/base-builder.js +112 -0
  32. package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
  33. package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
  34. package/infrastructure/domains/shared/environment-builder.js +118 -0
  35. package/infrastructure/domains/shared/environment-builder.test.js +246 -0
  36. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +366 -0
  37. package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
  38. package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
  39. package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
  40. package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
  41. package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
  42. package/infrastructure/domains/shared/resource-discovery.js +132 -0
  43. package/infrastructure/domains/shared/resource-discovery.test.js +410 -0
  44. package/infrastructure/domains/shared/utilities/base-definition-factory.js +2 -3
  45. package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
  46. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -0
  47. package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +259 -0
  48. package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
  49. package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +134 -0
  50. package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
  51. package/infrastructure/esbuild.config.js +53 -0
  52. package/infrastructure/infrastructure-composer.js +85 -0
  53. package/infrastructure/scripts/build-prisma-layer.js +60 -47
  54. package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
  55. package/layers/prisma/nodejs/package.json +8 -0
  56. package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
  57. package/package.json +8 -8
  58. package/infrastructure/aws-discovery.js +0 -1704
  59. package/infrastructure/aws-discovery.test.js +0 -1666
  60. package/infrastructure/serverless-template.js +0 -2804
  61. package/infrastructure/serverless-template.test.js +0 -1897
  62. /package/infrastructure/{POSTGRES-CONFIGURATION.md → docs/POSTGRES-CONFIGURATION.md} +0 -0
  63. /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
  64. /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
  65. /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
  66. /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
  67. /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
  68. /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
  69. /package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +0 -0
  70. /package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +0 -0
  71. /package/infrastructure/{run-discovery.js → scripts/run-discovery.js} +0 -0
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Tests for Handler Path Resolver Utility
3
+ *
4
+ * Tests offline mode handler path resolution
5
+ */
6
+
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const { findNodeModulesPath, modifyHandlerPaths } = require('./handler-path-resolver');
10
+
11
+ // Mock fs and child_process
12
+ jest.mock('fs');
13
+ jest.mock('node:child_process', () => ({
14
+ execSync: jest.fn(),
15
+ }));
16
+
17
+ describe('Handler Path Resolver', () => {
18
+ let originalArgv;
19
+ let originalCwd;
20
+
21
+ beforeEach(() => {
22
+ originalArgv = process.argv;
23
+ originalCwd = process.cwd;
24
+ jest.clearAllMocks();
25
+ });
26
+
27
+ afterEach(() => {
28
+ process.argv = originalArgv;
29
+ process.cwd = originalCwd;
30
+ });
31
+
32
+ describe('findNodeModulesPath()', () => {
33
+ it('should find node_modules in current directory (method 1)', () => {
34
+ const mockCwd = '/project';
35
+ process.cwd = jest.fn().mockReturnValue(mockCwd);
36
+ fs.existsSync = jest.fn().mockImplementation((p) =>
37
+ p === path.join(mockCwd, 'node_modules')
38
+ );
39
+
40
+ const result = findNodeModulesPath();
41
+
42
+ expect(result).toBe(path.join(mockCwd, 'node_modules'));
43
+ });
44
+
45
+ it('should search parent directories if not found in current (method 1)', () => {
46
+ const mockCwd = '/project/nested/deep';
47
+ process.cwd = jest.fn().mockReturnValue(mockCwd);
48
+ fs.existsSync = jest.fn().mockImplementation((p) =>
49
+ p === '/project/node_modules'
50
+ );
51
+
52
+ const result = findNodeModulesPath();
53
+
54
+ expect(result).toBe('/project/node_modules');
55
+ expect(fs.existsSync).toHaveBeenCalledWith('/project/nested/deep/node_modules');
56
+ expect(fs.existsSync).toHaveBeenCalledWith('/project/nested/node_modules');
57
+ expect(fs.existsSync).toHaveBeenCalledWith('/project/node_modules');
58
+ });
59
+
60
+ it('should use npm root if directory search fails (method 2)', () => {
61
+ process.cwd = jest.fn().mockReturnValue('/project');
62
+ fs.existsSync = jest.fn().mockReturnValue(false);
63
+
64
+ const { execSync } = require('node:child_process');
65
+ execSync.mockReturnValue('/project/node_modules\n');
66
+
67
+ // On second call for npm root, return true
68
+ fs.existsSync.mockImplementation((p) =>
69
+ p === '/project/node_modules'
70
+ );
71
+
72
+ const result = findNodeModulesPath();
73
+
74
+ expect(result).toBe('/project/node_modules');
75
+ expect(execSync).toHaveBeenCalledWith('npm root', { encoding: 'utf8' });
76
+ });
77
+
78
+ it('should handle npm root errors gracefully', () => {
79
+ process.cwd = jest.fn().mockReturnValue('/project');
80
+ fs.existsSync = jest.fn().mockReturnValue(false);
81
+
82
+ const { execSync } = require('node:child_process');
83
+ execSync.mockImplementation(() => {
84
+ throw new Error('npm not found');
85
+ });
86
+
87
+ const result = findNodeModulesPath();
88
+
89
+ // Should fall back to default
90
+ expect(result).toBe(path.resolve('/project', '../node_modules'));
91
+ });
92
+
93
+ it('should search from package.json locations (method 3)', () => {
94
+ process.cwd = jest.fn().mockReturnValue('/project/workspace');
95
+
96
+ let callCount = 0;
97
+ fs.existsSync = jest.fn().mockImplementation((p) => {
98
+ callCount++;
99
+ // First 5 calls fail (directory search)
100
+ if (callCount <= 5) return false;
101
+ // Package.json search
102
+ if (p === '/project/package.json') return true;
103
+ if (p === '/project/node_modules') return true;
104
+ return false;
105
+ });
106
+
107
+ const { execSync } = require('node:child_process');
108
+ execSync.mockImplementation(() => {
109
+ throw new Error('npm root failed');
110
+ });
111
+
112
+ const result = findNodeModulesPath();
113
+
114
+ expect(result).toBe('/project/node_modules');
115
+ });
116
+
117
+ it('should fall back to default path if all methods fail', () => {
118
+ process.cwd = jest.fn().mockReturnValue('/project');
119
+ fs.existsSync = jest.fn().mockReturnValue(false);
120
+
121
+ const { execSync } = require('node:child_process');
122
+ execSync.mockImplementation(() => {
123
+ throw new Error('npm root failed');
124
+ });
125
+
126
+ const result = findNodeModulesPath();
127
+
128
+ expect(result).toBe(path.resolve('/project', '../node_modules'));
129
+ });
130
+
131
+ it('should handle errors during search', () => {
132
+ process.cwd = jest.fn().mockImplementation(() => {
133
+ throw new Error('cwd error');
134
+ });
135
+
136
+ const result = findNodeModulesPath();
137
+
138
+ expect(result).toBe(path.resolve(process.cwd(), '../node_modules'));
139
+ });
140
+ });
141
+
142
+ describe('modifyHandlerPaths()', () => {
143
+ it('should not modify paths when not in offline mode', () => {
144
+ process.argv = ['node', 'test'];
145
+
146
+ const functions = {
147
+ auth: {
148
+ handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
149
+ },
150
+ };
151
+
152
+ const result = modifyHandlerPaths(functions);
153
+
154
+ expect(result.auth.handler).toBe('node_modules/@friggframework/core/handlers/routers/auth.handler');
155
+ });
156
+
157
+ it('should modify handler paths in offline mode', () => {
158
+ process.argv = ['node', 'test', 'offline'];
159
+ process.cwd = jest.fn().mockReturnValue('/project');
160
+ fs.existsSync = jest.fn().mockImplementation((p) =>
161
+ p === '/project/node_modules'
162
+ );
163
+
164
+ const functions = {
165
+ auth: {
166
+ handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
167
+ },
168
+ };
169
+
170
+ const result = modifyHandlerPaths(functions);
171
+
172
+ expect(result.auth.handler).toBe('node_modules/@friggframework/core/handlers/routers/auth.handler');
173
+ });
174
+
175
+ it('should handle functions without handlers', () => {
176
+ process.argv = ['node', 'test', 'offline'];
177
+
178
+ const functions = {
179
+ noHandler: {
180
+ events: [{ http: { path: '/test' } }],
181
+ },
182
+ };
183
+
184
+ const result = modifyHandlerPaths(functions);
185
+
186
+ expect(result.noHandler.handler).toBeUndefined();
187
+ });
188
+
189
+ it('should only modify handlers with node_modules path', () => {
190
+ process.argv = ['node', 'test', 'offline'];
191
+ process.cwd = jest.fn().mockReturnValue('/project');
192
+ fs.existsSync = jest.fn().mockImplementation((p) =>
193
+ p === '/project/node_modules'
194
+ );
195
+
196
+ const functions = {
197
+ coreHandler: {
198
+ handler: 'node_modules/@friggframework/core/handlers/auth.handler',
199
+ },
200
+ customHandler: {
201
+ handler: 'src/handlers/custom.handler',
202
+ },
203
+ };
204
+
205
+ const result = modifyHandlerPaths(functions);
206
+
207
+ expect(result.coreHandler.handler).toContain('node_modules');
208
+ expect(result.customHandler.handler).toBe('src/handlers/custom.handler');
209
+ });
210
+
211
+ it('should not mutate original functions object', () => {
212
+ process.argv = ['node', 'test', 'offline'];
213
+ process.cwd = jest.fn().mockReturnValue('/project');
214
+ fs.existsSync = jest.fn().mockImplementation((p) =>
215
+ p === '/project/node_modules'
216
+ );
217
+
218
+ const original = {
219
+ auth: {
220
+ handler: 'node_modules/@friggframework/core/handlers/auth.handler',
221
+ },
222
+ };
223
+
224
+ const result = modifyHandlerPaths(original);
225
+
226
+ // Result should be a copy
227
+ expect(result).not.toBe(original);
228
+ expect(result.auth).not.toBe(original.auth);
229
+ });
230
+
231
+ it('should handle multiple functions', () => {
232
+ process.argv = ['node', 'test', 'offline'];
233
+ process.cwd = jest.fn().mockReturnValue('/project');
234
+ fs.existsSync = jest.fn().mockImplementation((p) =>
235
+ p === '/project/node_modules'
236
+ );
237
+
238
+ const functions = {
239
+ auth: {
240
+ handler: 'node_modules/@friggframework/core/handlers/auth.handler',
241
+ },
242
+ user: {
243
+ handler: 'node_modules/@friggframework/core/handlers/user.handler',
244
+ },
245
+ health: {
246
+ handler: 'node_modules/@friggframework/core/handlers/health.handler',
247
+ },
248
+ };
249
+
250
+ const result = modifyHandlerPaths(functions);
251
+
252
+ expect(Object.keys(result)).toHaveLength(3);
253
+ expect(result.auth.handler).toContain('node_modules');
254
+ expect(result.user.handler).toContain('node_modules');
255
+ expect(result.health.handler).toContain('node_modules');
256
+ });
257
+ });
258
+ });
259
+
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Prisma Lambda Layer Manager
3
+ *
4
+ * Utility Layer - Hexagonal Architecture
5
+ *
6
+ * Manages Prisma Lambda Layer for serverless deployments.
7
+ * Ensures the layer exists and is built before deployment.
8
+ */
9
+
10
+ const path = require('path');
11
+ const fs = require('fs');
12
+ const { buildPrismaLayer } = require('../../../scripts/build-prisma-layer');
13
+
14
+ /**
15
+ * Ensure Prisma Lambda Layer exists
16
+ *
17
+ * Automatically builds the layer if it doesn't exist.
18
+ * The layer contains ONLY the Prisma runtime client (minimal, ~10-15MB).
19
+ * Prisma CLI is bundled separately in the dbMigrate function.
20
+ *
21
+ * @param {Object} databaseConfig - Database configuration from app definition
22
+ * @returns {Promise<void>}
23
+ * @throws {Error} If layer build fails
24
+ */
25
+ async function ensurePrismaLayerExists(databaseConfig = {}) {
26
+ const projectRoot = process.cwd();
27
+ const layerPath = path.join(projectRoot, 'layers/prisma');
28
+
29
+ // Check if layer already exists
30
+ if (fs.existsSync(layerPath)) {
31
+ console.log('✓ Prisma Lambda Layer already exists at', layerPath);
32
+ return;
33
+ }
34
+
35
+ // Layer doesn't exist - build it automatically
36
+ console.log('📦 Prisma Lambda Layer not found - building automatically...');
37
+ console.log(' Building MINIMAL layer (runtime only, NO CLI)');
38
+ console.log(' CLI is packaged separately in dbMigrate function');
39
+ console.log(' This may take a minute on first deployment.\n');
40
+
41
+ try {
42
+ // Build layer WITHOUT CLI (runtime only for minimal size)
43
+ await buildPrismaLayer(databaseConfig);
44
+ console.log('✓ Prisma Lambda Layer built successfully (~10-15MB)\n');
45
+ } catch (error) {
46
+ console.error('✗ Failed to build Prisma Lambda Layer:', error.message);
47
+ console.error(' You may need to run: npm install @friggframework/core\n');
48
+ throw error;
49
+ }
50
+ }
51
+
52
+ module.exports = {
53
+ ensurePrismaLayerExists,
54
+ };
55
+
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Tests for Prisma Layer Manager
3
+ *
4
+ * Tests Prisma Lambda Layer existence checking and building
5
+ */
6
+
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const { ensurePrismaLayerExists } = require('./prisma-layer-manager');
10
+
11
+ // Mock fs and buildPrismaLayer
12
+ jest.mock('fs');
13
+ jest.mock('../../../scripts/build-prisma-layer', () => ({
14
+ buildPrismaLayer: jest.fn(),
15
+ }));
16
+
17
+ const { buildPrismaLayer } = require('../../../scripts/build-prisma-layer');
18
+
19
+ describe('Prisma Layer Manager', () => {
20
+ let originalCwd;
21
+
22
+ beforeEach(() => {
23
+ originalCwd = process.cwd;
24
+ process.cwd = jest.fn().mockReturnValue('/project');
25
+ jest.clearAllMocks();
26
+ });
27
+
28
+ afterEach(() => {
29
+ process.cwd = originalCwd;
30
+ });
31
+
32
+ describe('ensurePrismaLayerExists()', () => {
33
+ it('should skip build if layer already exists', async () => {
34
+ fs.existsSync = jest.fn().mockReturnValue(true);
35
+
36
+ await ensurePrismaLayerExists();
37
+
38
+ expect(fs.existsSync).toHaveBeenCalledWith('/project/layers/prisma');
39
+ expect(buildPrismaLayer).not.toHaveBeenCalled();
40
+ });
41
+
42
+ it('should build layer if it does not exist', async () => {
43
+ fs.existsSync = jest.fn().mockReturnValue(false);
44
+ buildPrismaLayer.mockResolvedValue();
45
+
46
+ await ensurePrismaLayerExists();
47
+
48
+ expect(buildPrismaLayer).toHaveBeenCalledTimes(1);
49
+ });
50
+
51
+ it('should pass database config to buildPrismaLayer', async () => {
52
+ fs.existsSync = jest.fn().mockReturnValue(false);
53
+ buildPrismaLayer.mockResolvedValue();
54
+
55
+ const databaseConfig = {
56
+ postgres: { enable: true },
57
+ };
58
+
59
+ await ensurePrismaLayerExists(databaseConfig);
60
+
61
+ expect(buildPrismaLayer).toHaveBeenCalledWith(databaseConfig);
62
+ });
63
+
64
+ it('should handle build errors and rethrow', async () => {
65
+ fs.existsSync = jest.fn().mockReturnValue(false);
66
+ buildPrismaLayer.mockRejectedValue(new Error('Build failed'));
67
+
68
+ await expect(ensurePrismaLayerExists()).rejects.toThrow('Build failed');
69
+ });
70
+
71
+ it('should default to empty database config', async () => {
72
+ fs.existsSync = jest.fn().mockReturnValue(false);
73
+ buildPrismaLayer.mockResolvedValue();
74
+
75
+ await ensurePrismaLayerExists();
76
+
77
+ expect(buildPrismaLayer).toHaveBeenCalledWith({});
78
+ });
79
+
80
+ it('should use correct layer path relative to project root', async () => {
81
+ process.cwd = jest.fn().mockReturnValue('/custom/project/path');
82
+ fs.existsSync = jest.fn().mockReturnValue(true);
83
+
84
+ await ensurePrismaLayerExists();
85
+
86
+ expect(fs.existsSync).toHaveBeenCalledWith('/custom/project/path/layers/prisma');
87
+ });
88
+
89
+ it('should log success when layer already exists', async () => {
90
+ fs.existsSync = jest.fn().mockReturnValue(true);
91
+ const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
92
+
93
+ await ensurePrismaLayerExists();
94
+
95
+ expect(consoleSpy).toHaveBeenCalledWith(
96
+ expect.stringContaining('already exists')
97
+ );
98
+
99
+ consoleSpy.mockRestore();
100
+ });
101
+
102
+ it('should log build progress when building layer', async () => {
103
+ fs.existsSync = jest.fn().mockReturnValue(false);
104
+ buildPrismaLayer.mockResolvedValue();
105
+ const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
106
+
107
+ await ensurePrismaLayerExists();
108
+
109
+ expect(consoleSpy).toHaveBeenCalledWith(
110
+ expect.stringContaining('building automatically')
111
+ );
112
+ expect(consoleSpy).toHaveBeenCalledWith(
113
+ expect.stringContaining('built successfully')
114
+ );
115
+
116
+ consoleSpy.mockRestore();
117
+ });
118
+
119
+ it('should log error message on build failure', async () => {
120
+ fs.existsSync = jest.fn().mockReturnValue(false);
121
+ buildPrismaLayer.mockRejectedValue(new Error('Build error'));
122
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
123
+
124
+ await expect(ensurePrismaLayerExists()).rejects.toThrow();
125
+
126
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
127
+ expect.stringContaining('Failed to build')
128
+ );
129
+
130
+ consoleErrorSpy.mockRestore();
131
+ });
132
+ });
133
+ });
134
+
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Tests for Environment Variable Validator
3
+ *
4
+ * Tests validation of required environment variables
5
+ */
6
+
7
+ const { validateEnvironmentVariables } = require('./env-validator');
8
+
9
+ describe('Environment Validator', () => {
10
+ let originalEnv;
11
+
12
+ beforeEach(() => {
13
+ originalEnv = { ...process.env };
14
+ // Clear test-related env vars
15
+ delete process.env.TEST_VAR_1;
16
+ delete process.env.TEST_VAR_2;
17
+ delete process.env.NODE_ENV;
18
+ });
19
+
20
+ afterEach(() => {
21
+ process.env = originalEnv;
22
+ });
23
+
24
+ describe('validateEnvironmentVariables()', () => {
25
+ it('should return empty results for app definition without environment', () => {
26
+ const appDefinition = {};
27
+
28
+ const result = validateEnvironmentVariables(appDefinition);
29
+
30
+ expect(result.valid).toEqual([]);
31
+ expect(result.missing).toEqual([]);
32
+ expect(result.warnings).toEqual([]);
33
+ });
34
+
35
+ it('should validate present environment variables', () => {
36
+ process.env.TEST_VAR_1 = 'value1';
37
+ process.env.TEST_VAR_2 = 'value2';
38
+
39
+ const appDefinition = {
40
+ environment: {
41
+ TEST_VAR_1: true,
42
+ TEST_VAR_2: true,
43
+ },
44
+ };
45
+
46
+ const result = validateEnvironmentVariables(appDefinition);
47
+
48
+ expect(result.valid).toEqual(['TEST_VAR_1', 'TEST_VAR_2']);
49
+ expect(result.missing).toEqual([]);
50
+ });
51
+
52
+ it('should detect missing environment variables', () => {
53
+ process.env.TEST_VAR_1 = 'value1';
54
+ // TEST_VAR_2 not set
55
+
56
+ const appDefinition = {
57
+ environment: {
58
+ TEST_VAR_1: true,
59
+ TEST_VAR_2: true,
60
+ },
61
+ };
62
+
63
+ const result = validateEnvironmentVariables(appDefinition);
64
+
65
+ expect(result.valid).toEqual(['TEST_VAR_1']);
66
+ expect(result.missing).toEqual(['TEST_VAR_2']);
67
+ });
68
+
69
+ it('should only validate variables with value true', () => {
70
+ process.env.TEST_VAR_1 = 'value1';
71
+
72
+ const appDefinition = {
73
+ environment: {
74
+ TEST_VAR_1: true,
75
+ TEST_VAR_2: false,
76
+ TEST_VAR_3: 'string-value',
77
+ },
78
+ };
79
+
80
+ const result = validateEnvironmentVariables(appDefinition);
81
+
82
+ expect(result.valid).toEqual(['TEST_VAR_1']);
83
+ expect(result.missing).toEqual([]);
84
+ });
85
+
86
+ it('should handle NODE_ENV specially with warning', () => {
87
+ // NODE_ENV not set
88
+
89
+ const appDefinition = {
90
+ environment: {
91
+ NODE_ENV: true,
92
+ },
93
+ };
94
+
95
+ const result = validateEnvironmentVariables(appDefinition);
96
+
97
+ expect(result.missing).not.toContain('NODE_ENV');
98
+ expect(result.warnings).toContain('NODE_ENV not set, defaulting to "production"');
99
+ });
100
+
101
+ it('should not warn about NODE_ENV if it is set', () => {
102
+ process.env.NODE_ENV = 'development';
103
+
104
+ const appDefinition = {
105
+ environment: {
106
+ NODE_ENV: true,
107
+ },
108
+ };
109
+
110
+ const result = validateEnvironmentVariables(appDefinition);
111
+
112
+ expect(result.valid).toContain('NODE_ENV');
113
+ expect(result.warnings).not.toContain('NODE_ENV not set, defaulting to "production"');
114
+ });
115
+
116
+ it('should warn about missing variables', () => {
117
+ const appDefinition = {
118
+ environment: {
119
+ MISSING_VAR_1: true,
120
+ MISSING_VAR_2: true,
121
+ },
122
+ };
123
+
124
+ const result = validateEnvironmentVariables(appDefinition);
125
+
126
+ expect(result.missing).toEqual(['MISSING_VAR_1', 'MISSING_VAR_2']);
127
+ expect(result.warnings.length).toBeGreaterThan(0);
128
+ });
129
+
130
+ it('should handle mixed valid and missing variables', () => {
131
+ process.env.VALID_VAR = 'value';
132
+
133
+ const appDefinition = {
134
+ environment: {
135
+ VALID_VAR: true,
136
+ MISSING_VAR: true,
137
+ },
138
+ };
139
+
140
+ const result = validateEnvironmentVariables(appDefinition);
141
+
142
+ expect(result.valid).toEqual(['VALID_VAR']);
143
+ expect(result.missing).toEqual(['MISSING_VAR']);
144
+ });
145
+
146
+ it('should handle empty environment object', () => {
147
+ const appDefinition = {
148
+ environment: {},
149
+ };
150
+
151
+ const result = validateEnvironmentVariables(appDefinition);
152
+
153
+ expect(result.valid).toEqual([]);
154
+ expect(result.missing).toEqual([]);
155
+ });
156
+
157
+ it('should handle environment variables with empty string values', () => {
158
+ process.env.TEST_VAR = '';
159
+
160
+ const appDefinition = {
161
+ environment: {
162
+ TEST_VAR: true,
163
+ },
164
+ };
165
+
166
+ const result = validateEnvironmentVariables(appDefinition);
167
+
168
+ // Empty string is still considered "present"
169
+ expect(result.valid).toContain('TEST_VAR');
170
+ });
171
+ });
172
+ });
173
+
@@ -0,0 +1,53 @@
1
+ /**
2
+ * esbuild Configuration for Frigg Lambda Functions
3
+ *
4
+ * Optimized for AWS Lambda Node.js 22.x runtime with:
5
+ * - Tree-shaking for minimal bundle size
6
+ * - AWS SDK v3 externalization (provided by Lambda)
7
+ * - Prisma client externalization (in Lambda layer)
8
+ * - Source maps for debugging
9
+ */
10
+
11
+ module.exports = {
12
+ bundle: true,
13
+ minify: true,
14
+ sourcemap: true,
15
+ target: 'node22', // AWS Lambda Node.js 22.x (latest supported)
16
+ platform: 'node',
17
+ format: 'cjs', // CommonJS for Lambda
18
+ mainFields: ['main', 'module'],
19
+
20
+ // External packages - not included in bundle
21
+ external: [
22
+ // AWS SDK v3 - provided by Lambda runtime
23
+ '@aws-sdk/*',
24
+ 'aws-sdk', // Legacy v2 SDK
25
+
26
+ // Prisma - in Lambda layer
27
+ '@prisma/client',
28
+ 'prisma',
29
+ '.prisma/*',
30
+
31
+ // Native modules
32
+ 'sharp',
33
+ 'canvas',
34
+ 'sqlite3',
35
+ 'pg-native',
36
+ ],
37
+
38
+ // Package manager
39
+ packager: 'npm',
40
+
41
+ // Additional esbuild options
42
+ keepNames: true, // Preserve function names for debugging
43
+ metafile: true, // Generate build metadata
44
+
45
+ // Exclude patterns (for serverless-esbuild plugin)
46
+ exclude: [
47
+ 'aws-sdk',
48
+ '@aws-sdk/*',
49
+ '@prisma/client',
50
+ 'prisma',
51
+ ],
52
+ };
53
+