@friggframework/devtools 2.0.0--canary.461.ec909cf.0 → 2.0.0--canary.461.7b36f0f.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.
- package/frigg-cli/__tests__/unit/commands/build.test.js +6 -6
- package/frigg-cli/build-command/index.js +1 -1
- package/frigg-cli/deploy-command/index.js +6 -6
- package/frigg-cli/generate-command/index.js +2 -2
- package/frigg-cli/generate-iam-command.js +10 -10
- package/frigg-cli/start-command/index.js +1 -1
- package/frigg-cli/start-command/start-command.test.js +3 -3
- package/frigg-cli/utils/database-validator.js +14 -21
- package/infrastructure/REFACTOR.md +532 -0
- package/infrastructure/TRANSFORMATION-VISUAL.md +239 -0
- package/infrastructure/__tests__/postgres-config.test.js +1 -1
- package/infrastructure/create-frigg-infrastructure.js +1 -1
- package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
- package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
- package/infrastructure/domains/database/aurora-discovery.js +81 -0
- package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
- package/infrastructure/domains/integration/integration-builder.js +178 -0
- package/infrastructure/domains/integration/integration-builder.test.js +362 -0
- package/infrastructure/domains/integration/websocket-builder.js +69 -0
- package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
- package/infrastructure/domains/networking/vpc-discovery.test.js +257 -0
- package/infrastructure/domains/parameters/ssm-builder.js +79 -0
- package/infrastructure/domains/parameters/ssm-builder.test.js +188 -0
- package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
- package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
- package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
- package/infrastructure/domains/security/kms-builder.js +169 -0
- package/infrastructure/domains/security/kms-builder.test.js +354 -0
- package/infrastructure/domains/security/kms-discovery.js +80 -0
- package/infrastructure/domains/security/kms-discovery.test.js +176 -0
- package/infrastructure/domains/shared/base-builder.js +112 -0
- package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
- package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
- package/infrastructure/domains/shared/environment-builder.js +118 -0
- package/infrastructure/domains/shared/environment-builder.test.js +246 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +366 -0
- package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
- package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
- package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
- package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
- package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
- package/infrastructure/domains/shared/resource-discovery.js +132 -0
- package/infrastructure/domains/shared/resource-discovery.test.js +410 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +259 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +134 -0
- package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
- package/infrastructure/esbuild.config.js +53 -0
- package/infrastructure/infrastructure-composer.js +85 -0
- package/infrastructure/scripts/build-prisma-layer.js +60 -47
- package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
- package/layers/prisma/nodejs/package.json +8 -0
- package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
- package/package.json +8 -8
- package/infrastructure/aws-discovery.js +0 -1704
- package/infrastructure/aws-discovery.test.js +0 -1666
- package/infrastructure/serverless-template.js +0 -2804
- package/infrastructure/serverless-template.test.js +0 -1897
- /package/infrastructure/{POSTGRES-CONFIGURATION.md → docs/POSTGRES-CONFIGURATION.md} +0 -0
- /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
- /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
- /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
- /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
- /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
- /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
- /package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +0 -0
- /package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +0 -0
- /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
|
+
|