@friggframework/devtools 2.0.0-next.62 → 2.0.0-next.63

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 (165) hide show
  1. package/infrastructure/ARCHITECTURE.md +487 -0
  2. package/infrastructure/CLAUDE.md +481 -0
  3. package/infrastructure/HEALTH.md +468 -0
  4. package/infrastructure/README.md +522 -0
  5. package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
  6. package/infrastructure/__tests__/helpers/test-utils.js +277 -0
  7. package/infrastructure/__tests__/postgres-config.test.js +914 -0
  8. package/infrastructure/__tests__/template-generation.test.js +687 -0
  9. package/infrastructure/create-frigg-infrastructure.js +147 -0
  10. package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
  11. package/infrastructure/docs/PRE-DEPLOYMENT-HEALTH-CHECK-SPEC.md +1317 -0
  12. package/infrastructure/docs/WEBSOCKET-CONFIGURATION.md +105 -0
  13. package/infrastructure/docs/deployment-instructions.md +268 -0
  14. package/infrastructure/docs/generate-iam-command.md +278 -0
  15. package/infrastructure/docs/iam-policy-templates.md +193 -0
  16. package/infrastructure/domains/database/aurora-builder.js +809 -0
  17. package/infrastructure/domains/database/aurora-builder.test.js +950 -0
  18. package/infrastructure/domains/database/aurora-discovery.js +87 -0
  19. package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
  20. package/infrastructure/domains/database/aurora-resolver.js +210 -0
  21. package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
  22. package/infrastructure/domains/database/migration-builder.js +701 -0
  23. package/infrastructure/domains/database/migration-builder.test.js +321 -0
  24. package/infrastructure/domains/database/migration-resolver.js +163 -0
  25. package/infrastructure/domains/database/migration-resolver.test.js +337 -0
  26. package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
  27. package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
  28. package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
  29. package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
  30. package/infrastructure/domains/health/application/ports/index.js +26 -0
  31. package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
  32. package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
  33. package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
  34. package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
  35. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
  36. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
  37. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
  38. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
  39. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
  40. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
  41. package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
  42. package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
  43. package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
  44. package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
  45. package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
  46. package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
  47. package/infrastructure/domains/health/domain/entities/issue.js +299 -0
  48. package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
  49. package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
  50. package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
  51. package/infrastructure/domains/health/domain/entities/resource.js +159 -0
  52. package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
  53. package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
  54. package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
  55. package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
  56. package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
  57. package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
  58. package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
  59. package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
  60. package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
  61. package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
  62. package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
  63. package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
  64. package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
  65. package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
  66. package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
  67. package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
  68. package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
  69. package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
  70. package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
  71. package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
  72. package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
  73. package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
  74. package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
  75. package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
  76. package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
  77. package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
  78. package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
  79. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
  80. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
  81. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
  82. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
  83. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
  84. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
  85. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
  86. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
  87. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
  88. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
  89. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
  90. package/infrastructure/domains/integration/integration-builder.js +404 -0
  91. package/infrastructure/domains/integration/integration-builder.test.js +690 -0
  92. package/infrastructure/domains/integration/integration-resolver.js +170 -0
  93. package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
  94. package/infrastructure/domains/integration/websocket-builder.js +69 -0
  95. package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
  96. package/infrastructure/domains/networking/vpc-builder.js +2051 -0
  97. package/infrastructure/domains/networking/vpc-builder.test.js +1960 -0
  98. package/infrastructure/domains/networking/vpc-discovery.js +177 -0
  99. package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
  100. package/infrastructure/domains/networking/vpc-resolver.js +505 -0
  101. package/infrastructure/domains/networking/vpc-resolver.test.js +801 -0
  102. package/infrastructure/domains/parameters/ssm-builder.js +79 -0
  103. package/infrastructure/domains/parameters/ssm-builder.test.js +189 -0
  104. package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
  105. package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
  106. package/infrastructure/domains/security/iam-generator.js +816 -0
  107. package/infrastructure/domains/security/iam-generator.test.js +204 -0
  108. package/infrastructure/domains/security/kms-builder.js +415 -0
  109. package/infrastructure/domains/security/kms-builder.test.js +392 -0
  110. package/infrastructure/domains/security/kms-discovery.js +80 -0
  111. package/infrastructure/domains/security/kms-discovery.test.js +177 -0
  112. package/infrastructure/domains/security/kms-resolver.js +96 -0
  113. package/infrastructure/domains/security/kms-resolver.test.js +216 -0
  114. package/infrastructure/domains/security/templates/frigg-deployment-iam-stack.yaml +401 -0
  115. package/infrastructure/domains/security/templates/iam-policy-basic.json +218 -0
  116. package/infrastructure/domains/security/templates/iam-policy-full.json +288 -0
  117. package/infrastructure/domains/shared/base-builder.js +112 -0
  118. package/infrastructure/domains/shared/base-resolver.js +186 -0
  119. package/infrastructure/domains/shared/base-resolver.test.js +305 -0
  120. package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
  121. package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
  122. package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
  123. package/infrastructure/domains/shared/cloudformation-discovery.js +672 -0
  124. package/infrastructure/domains/shared/cloudformation-discovery.test.js +985 -0
  125. package/infrastructure/domains/shared/environment-builder.js +119 -0
  126. package/infrastructure/domains/shared/environment-builder.test.js +247 -0
  127. package/infrastructure/domains/shared/providers/aws-provider-adapter.js +579 -0
  128. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +416 -0
  129. package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
  130. package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
  131. package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
  132. package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
  133. package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
  134. package/infrastructure/domains/shared/resource-discovery.enhanced.test.js +306 -0
  135. package/infrastructure/domains/shared/resource-discovery.js +233 -0
  136. package/infrastructure/domains/shared/resource-discovery.test.js +588 -0
  137. package/infrastructure/domains/shared/types/app-definition.js +205 -0
  138. package/infrastructure/domains/shared/types/discovery-result.js +106 -0
  139. package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
  140. package/infrastructure/domains/shared/types/index.js +46 -0
  141. package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
  142. package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
  143. package/infrastructure/domains/shared/utilities/base-definition-factory.js +408 -0
  144. package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
  145. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +291 -0
  146. package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
  147. package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
  148. package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +159 -0
  149. package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +444 -0
  150. package/infrastructure/domains/shared/validation/env-validator.js +78 -0
  151. package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
  152. package/infrastructure/domains/shared/validation/plugin-validator.js +187 -0
  153. package/infrastructure/domains/shared/validation/plugin-validator.test.js +323 -0
  154. package/infrastructure/esbuild.config.js +53 -0
  155. package/infrastructure/index.js +4 -0
  156. package/infrastructure/infrastructure-composer.js +117 -0
  157. package/infrastructure/infrastructure-composer.test.js +1895 -0
  158. package/infrastructure/integration.test.js +383 -0
  159. package/infrastructure/scripts/build-prisma-layer.js +701 -0
  160. package/infrastructure/scripts/build-prisma-layer.test.js +170 -0
  161. package/infrastructure/scripts/build-time-discovery.js +238 -0
  162. package/infrastructure/scripts/build-time-discovery.test.js +379 -0
  163. package/infrastructure/scripts/run-discovery.js +110 -0
  164. package/infrastructure/scripts/verify-prisma-layer.js +72 -0
  165. package/package.json +8 -7
@@ -0,0 +1,379 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { BuildTimeDiscovery, runBuildTimeDiscovery } = require('./build-time-discovery');
4
+ const { AWSDiscovery } = require('./aws-discovery');
5
+
6
+ // Mock dependencies
7
+ jest.mock('fs');
8
+ // Mock will be updated when tests are rewritten for new provider architecture
9
+ // jest.mock('./aws-discovery');
10
+
11
+ describe('BuildTimeDiscovery', () => {
12
+ let buildTimeDiscovery;
13
+ let mockAWSDiscovery;
14
+ const originalEnv = process.env;
15
+
16
+ beforeEach(() => {
17
+ buildTimeDiscovery = new BuildTimeDiscovery('us-east-1');
18
+
19
+ // Mock AWSDiscovery
20
+ mockAWSDiscovery = {
21
+ discoverResources: jest.fn(),
22
+ };
23
+ AWSDiscovery.mockImplementation(() => mockAWSDiscovery);
24
+
25
+ // Mock fs methods
26
+ fs.writeFileSync = jest.fn();
27
+ fs.readFileSync = jest.fn();
28
+
29
+ // Reset environment
30
+ process.env = { ...originalEnv };
31
+
32
+ jest.clearAllMocks();
33
+ });
34
+
35
+ afterEach(() => {
36
+ process.env = originalEnv;
37
+ });
38
+
39
+ describe('constructor', () => {
40
+ it('should initialize with default region', () => {
41
+ const discovery = new BuildTimeDiscovery();
42
+ expect(discovery.region).toBe('us-east-1');
43
+ });
44
+
45
+ it('should initialize with custom region', () => {
46
+ const discovery = new BuildTimeDiscovery('us-west-2');
47
+ expect(discovery.region).toBe('us-west-2');
48
+ });
49
+
50
+ it('should use AWS_REGION environment variable', () => {
51
+ process.env.AWS_REGION = 'eu-west-1';
52
+ const discovery = new BuildTimeDiscovery();
53
+ expect(discovery.region).toBe('eu-west-1');
54
+ });
55
+ });
56
+
57
+ describe('discoverAndCreateConfig', () => {
58
+ const mockResources = {
59
+ defaultVpcId: 'vpc-12345678',
60
+ vpcCidr: '172.31.0.0/16',
61
+ defaultSecurityGroupId: 'sg-12345678',
62
+ privateSubnetId1: 'subnet-1',
63
+ privateSubnetId2: 'subnet-2',
64
+ privateRouteTableId: 'rtb-12345678',
65
+ defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678'
66
+ };
67
+
68
+ it('should discover resources and create config file', async () => {
69
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
70
+
71
+ const result = await buildTimeDiscovery.discoverAndCreateConfig('./test-config.json');
72
+
73
+ expect(mockAWSDiscovery.discoverResources).toHaveBeenCalled();
74
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
75
+ './test-config.json',
76
+ expect.stringContaining('"awsDiscovery"')
77
+ );
78
+ expect(result.awsDiscovery).toEqual(mockResources);
79
+ expect(result.region).toBe('us-east-1');
80
+ expect(result.generatedAt).toBeDefined();
81
+ });
82
+
83
+ it('should use default output path', async () => {
84
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
85
+
86
+ await buildTimeDiscovery.discoverAndCreateConfig();
87
+
88
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
89
+ './aws-discovery-config.json',
90
+ expect.any(String)
91
+ );
92
+ });
93
+
94
+ it('should throw error when discovery fails', async () => {
95
+ const error = new Error('Discovery failed');
96
+ mockAWSDiscovery.discoverResources.mockRejectedValue(error);
97
+
98
+ await expect(buildTimeDiscovery.discoverAndCreateConfig()).rejects.toThrow('Discovery failed');
99
+ });
100
+ });
101
+
102
+ describe('replaceTemplateVariables', () => {
103
+ const mockResources = {
104
+ defaultVpcId: 'vpc-12345678',
105
+ vpcCidr: '172.31.0.0/16',
106
+ defaultSecurityGroupId: 'sg-12345678',
107
+ privateSubnetId1: 'subnet-1',
108
+ privateSubnetId2: 'subnet-2',
109
+ privateRouteTableId: 'rtb-12345678',
110
+ defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678'
111
+ };
112
+
113
+ it('should replace all AWS discovery placeholders', () => {
114
+ const templateContent = `
115
+ vpc:
116
+ id: \${self:custom.awsDiscovery.defaultVpcId}
117
+ securityGroups:
118
+ - \${self:custom.awsDiscovery.defaultSecurityGroupId}
119
+ subnets:
120
+ - \${self:custom.awsDiscovery.privateSubnetId1}
121
+ - \${self:custom.awsDiscovery.privateSubnetId2}
122
+ routeTable: \${self:custom.awsDiscovery.privateRouteTableId}
123
+ kms:
124
+ keyId: \${self:custom.awsDiscovery.defaultKmsKeyId}
125
+ `;
126
+
127
+ const result = buildTimeDiscovery.replaceTemplateVariables(templateContent, mockResources);
128
+
129
+ expect(result).toContain('vpc-12345678');
130
+ expect(result).toContain('sg-12345678');
131
+ expect(result).toContain('subnet-1');
132
+ expect(result).toContain('subnet-2');
133
+ expect(result).toContain('rtb-12345678');
134
+ expect(result).toContain('arn:aws:kms:us-east-1:123456789012:key/12345678');
135
+ expect(result).not.toContain('${self:custom.awsDiscovery');
136
+ });
137
+
138
+ it('should handle content without placeholders', () => {
139
+ const templateContent = `
140
+ service: my-service
141
+ provider:
142
+ name: aws
143
+ runtime: nodejs18.x
144
+ `;
145
+
146
+ const result = buildTimeDiscovery.replaceTemplateVariables(templateContent, mockResources);
147
+
148
+ expect(result).toBe(templateContent);
149
+ });
150
+
151
+ it('should handle multiple occurrences of same placeholder', () => {
152
+ const templateContent = `
153
+ vpc1: \${self:custom.awsDiscovery.defaultVpcId}
154
+ vpc2: \${self:custom.awsDiscovery.defaultVpcId}
155
+ `;
156
+
157
+ const result = buildTimeDiscovery.replaceTemplateVariables(templateContent, mockResources);
158
+
159
+ expect(result).toBe(`
160
+ vpc1: vpc-12345678
161
+ vpc2: vpc-12345678
162
+ `);
163
+ });
164
+ });
165
+
166
+ describe('processServerlessConfig', () => {
167
+ const mockConfigContent = `
168
+ provider:
169
+ vpc: \${self:custom.awsDiscovery.defaultVpcId}
170
+ kms: \${self:custom.awsDiscovery.defaultKmsKeyId}
171
+ `;
172
+
173
+ const mockResources = {
174
+ defaultVpcId: 'vpc-12345678',
175
+ defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678'
176
+ };
177
+
178
+ it('should process serverless config and update file', async () => {
179
+ fs.readFileSync.mockReturnValue(mockConfigContent);
180
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
181
+
182
+ const result = await buildTimeDiscovery.processServerlessConfig('./serverless.yml');
183
+
184
+ expect(fs.readFileSync).toHaveBeenCalledWith('./serverless.yml', 'utf8');
185
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
186
+ './serverless.yml',
187
+ expect.stringContaining('vpc-12345678')
188
+ );
189
+ expect(result).toEqual(mockResources);
190
+ });
191
+
192
+ it('should write to different output file when specified', async () => {
193
+ fs.readFileSync.mockReturnValue(mockConfigContent);
194
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
195
+
196
+ await buildTimeDiscovery.processServerlessConfig('./serverless.yml', './serverless-processed.yml');
197
+
198
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
199
+ './serverless-processed.yml',
200
+ expect.any(String)
201
+ );
202
+ });
203
+
204
+ it('should throw error when file read fails', async () => {
205
+ fs.readFileSync.mockImplementation(() => {
206
+ throw new Error('File not found');
207
+ });
208
+
209
+ await expect(buildTimeDiscovery.processServerlessConfig('./nonexistent.yml')).rejects.toThrow('File not found');
210
+ });
211
+ });
212
+
213
+ describe('generateCustomSection', () => {
214
+ it('should generate custom section with discovered resources', () => {
215
+ const mockResources = {
216
+ defaultVpcId: 'vpc-12345678',
217
+ defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678'
218
+ };
219
+
220
+ const result = buildTimeDiscovery.generateCustomSection(mockResources);
221
+
222
+ expect(result).toEqual({
223
+ awsDiscovery: mockResources
224
+ });
225
+ });
226
+ });
227
+
228
+ describe('preBuildHook', () => {
229
+ const mockResources = {
230
+ defaultVpcId: 'vpc-12345678',
231
+ vpcCidr: '172.31.0.0/16',
232
+ defaultSecurityGroupId: 'sg-12345678',
233
+ privateSubnetId1: 'subnet-1',
234
+ privateSubnetId2: 'subnet-2',
235
+ privateRouteTableId: 'rtb-12345678',
236
+ defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678'
237
+ };
238
+
239
+ it('should run discovery when VPC is enabled', async () => {
240
+ const appDefinition = {
241
+ vpc: { enable: true },
242
+ integrations: []
243
+ };
244
+
245
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
246
+
247
+ const result = await buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1');
248
+
249
+ expect(mockAWSDiscovery.discoverResources).toHaveBeenCalled();
250
+ expect(result).toEqual(mockResources);
251
+ expect(process.env.AWS_DISCOVERY_VPC_ID).toBe('vpc-12345678');
252
+ expect(process.env.AWS_DISCOVERY_KMS_KEY_ID).toBe('arn:aws:kms:us-east-1:123456789012:key/12345678');
253
+ });
254
+
255
+ it('should run discovery when KMS is enabled', async () => {
256
+ const appDefinition = {
257
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
258
+ integrations: []
259
+ };
260
+
261
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
262
+
263
+ const result = await buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1');
264
+
265
+ expect(mockAWSDiscovery.discoverResources).toHaveBeenCalled();
266
+ expect(result).toEqual(mockResources);
267
+ });
268
+
269
+ it('should run discovery when SSM is enabled', async () => {
270
+ const appDefinition = {
271
+ ssm: { enable: true },
272
+ integrations: []
273
+ };
274
+
275
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
276
+
277
+ const result = await buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1');
278
+
279
+ expect(mockAWSDiscovery.discoverResources).toHaveBeenCalled();
280
+ expect(result).toEqual(mockResources);
281
+ });
282
+
283
+ it('should skip discovery when no features are enabled', async () => {
284
+ const appDefinition = {
285
+ integrations: []
286
+ };
287
+
288
+ const result = await buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1');
289
+
290
+ expect(mockAWSDiscovery.discoverResources).not.toHaveBeenCalled();
291
+ expect(result).toBeNull();
292
+ });
293
+
294
+ it('should throw error when discovery fails', async () => {
295
+ const appDefinition = {
296
+ vpc: { enable: true },
297
+ integrations: []
298
+ };
299
+
300
+ const error = new Error('Discovery failed');
301
+ mockAWSDiscovery.discoverResources.mockRejectedValue(error);
302
+
303
+ await expect(buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1')).rejects.toThrow('Discovery failed');
304
+ });
305
+
306
+ it('should set all environment variables', async () => {
307
+ const appDefinition = {
308
+ vpc: { enable: true },
309
+ integrations: []
310
+ };
311
+
312
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
313
+
314
+ await buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1');
315
+
316
+ expect(process.env.AWS_DISCOVERY_VPC_ID).toBe('vpc-12345678');
317
+ expect(process.env.AWS_DISCOVERY_SECURITY_GROUP_ID).toBe('sg-12345678');
318
+ expect(process.env.AWS_DISCOVERY_SUBNET_ID_1).toBe('subnet-1');
319
+ expect(process.env.AWS_DISCOVERY_SUBNET_ID_2).toBe('subnet-2');
320
+ expect(process.env.AWS_DISCOVERY_ROUTE_TABLE_ID).toBe('rtb-12345678');
321
+ expect(process.env.AWS_DISCOVERY_KMS_KEY_ID).toBe('arn:aws:kms:us-east-1:123456789012:key/12345678');
322
+ });
323
+ });
324
+
325
+ describe('runBuildTimeDiscovery', () => {
326
+ it('should run discovery with default options', async () => {
327
+ const mockResources = { defaultVpcId: 'vpc-12345678' };
328
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
329
+
330
+ const result = await runBuildTimeDiscovery();
331
+
332
+ expect(result.awsDiscovery).toEqual(mockResources);
333
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
334
+ './aws-discovery-config.json',
335
+ expect.any(String)
336
+ );
337
+ });
338
+
339
+ it('should process config file when configPath provided', async () => {
340
+ const mockConfigContent = 'provider: aws';
341
+ const mockResources = { defaultVpcId: 'vpc-12345678' };
342
+
343
+ fs.readFileSync.mockReturnValue(mockConfigContent);
344
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
345
+
346
+ const result = await runBuildTimeDiscovery({
347
+ configPath: './serverless.yml'
348
+ });
349
+
350
+ expect(result).toEqual(mockResources);
351
+ expect(fs.readFileSync).toHaveBeenCalledWith('./serverless.yml', 'utf8');
352
+ });
353
+
354
+ it('should use custom region and output path', async () => {
355
+ const mockResources = { defaultVpcId: 'vpc-12345678' };
356
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
357
+
358
+ await runBuildTimeDiscovery({
359
+ region: 'eu-west-1',
360
+ outputPath: './custom-config.json'
361
+ });
362
+
363
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
364
+ './custom-config.json',
365
+ expect.any(String)
366
+ );
367
+ });
368
+
369
+ it('should use AWS_REGION environment variable when region not specified', async () => {
370
+ process.env.AWS_REGION = 'ap-southeast-1';
371
+ const mockResources = { defaultVpcId: 'vpc-12345678' };
372
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
373
+
374
+ const result = await runBuildTimeDiscovery();
375
+
376
+ expect(result.region).toBe('ap-southeast-1');
377
+ });
378
+ });
379
+ });
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Pre-build script to run AWS discovery and set environment variables
5
+ * This should be run before serverless commands that need AWS resource discovery
6
+ */
7
+
8
+ const { BuildTimeDiscovery } = require('./build-time-discovery');
9
+ const { findNearestBackendPackageJson } = require('@friggframework/core');
10
+ const path = require('path');
11
+
12
+ async function runDiscovery() {
13
+ let appDefinition;
14
+
15
+ try {
16
+ console.log('🔍 Starting AWS resource discovery...');
17
+
18
+ // Find the backend package.json to get AppDefinition
19
+ const backendPath = findNearestBackendPackageJson();
20
+ if (!backendPath) {
21
+ console.log('⚠️ No backend package.json found, skipping discovery');
22
+ return;
23
+ }
24
+
25
+ const backendDir = path.dirname(backendPath);
26
+ const backendFilePath = path.join(backendDir, 'index.js');
27
+
28
+ if (!require('fs').existsSync(backendFilePath)) {
29
+ console.log('⚠️ No backend/index.js found, skipping discovery');
30
+ return;
31
+ }
32
+
33
+ // Load the app definition
34
+ const backend = require(backendFilePath);
35
+ appDefinition = backend.Definition;
36
+
37
+ if (!appDefinition) {
38
+ console.log('⚠️ No Definition found in backend/index.js, skipping discovery');
39
+ return;
40
+ }
41
+
42
+ // Check if discovery is needed
43
+ const needsDiscovery = appDefinition.vpc?.enable ||
44
+ appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
45
+ appDefinition.ssm?.enable;
46
+
47
+ if (!needsDiscovery) {
48
+ console.log('ℹ️ No AWS discovery needed based on app definition');
49
+ return;
50
+ }
51
+
52
+ console.log('📋 App requires AWS discovery for:');
53
+ if (appDefinition.vpc?.enable) console.log(' ✅ VPC support');
54
+ if (appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') console.log(' ✅ KMS encryption');
55
+ if (appDefinition.ssm?.enable) console.log(' ✅ SSM parameters');
56
+
57
+ // Run discovery
58
+ const discovery = new BuildTimeDiscovery();
59
+ const resources = await discovery.preBuildHook(appDefinition, process.env.AWS_REGION || 'us-east-1');
60
+
61
+ if (resources) {
62
+ console.log('✅ AWS discovery completed successfully!');
63
+ console.log(` VPC: ${resources.defaultVpcId}`);
64
+ console.log(` Subnets: ${resources.privateSubnetId1}, ${resources.privateSubnetId2}`);
65
+ console.log(` Public Subnet: ${resources.publicSubnetId}`);
66
+ console.log(` Security Group: ${resources.defaultSecurityGroupId}`);
67
+ console.log(` Route Table: ${resources.privateRouteTableId}`);
68
+ console.log(` KMS Key: ${resources.defaultKmsKeyId}`);
69
+ }
70
+
71
+ } catch (error) {
72
+ console.error('❌ AWS discovery failed:', error.message);
73
+ console.error('');
74
+
75
+ // Check if this is an AWS SDK missing error
76
+ if (error.message.includes('Cannot find module') && error.message.includes('@aws-sdk')) {
77
+ console.error('🚨 AWS SDK not installed!');
78
+ console.error('');
79
+ console.error('💡 Install AWS SDK dependencies:');
80
+ console.error(' npm install @aws-sdk/client-ec2 @aws-sdk/client-kms @aws-sdk/client-sts');
81
+ console.error('');
82
+ } else {
83
+ console.error('🚨 Discovery is required because your AppDefinition has these features enabled:');
84
+ if (appDefinition.vpc?.enable) console.error(' ❌ VPC support (vpc.enable: true)');
85
+ if (appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') console.error(' ❌ KMS encryption (encryption.fieldLevelEncryptionMethod: \'kms\')');
86
+ if (appDefinition.ssm?.enable) console.error(' ❌ SSM parameters (ssm.enable: true)');
87
+ console.error('');
88
+ console.error('💡 To fix this issue:');
89
+ console.error(' 1. Check AWS credentials: aws sts get-caller-identity');
90
+ console.error(' 2. Verify IAM permissions (see AWS-IAM-CREDENTIAL-NEEDS.md)');
91
+ console.error(' 3. Ensure default VPC exists: aws ec2 describe-vpcs');
92
+ console.error(' 4. Check AWS region: aws configure get region');
93
+ console.error('');
94
+ }
95
+
96
+ console.error('🔧 Or disable features in backend/index.js:');
97
+ console.error(' vpc: { enable: false }');
98
+ console.error(' encryption: { fieldLevelEncryptionMethod: \'aes\' }');
99
+ console.error(' ssm: { enable: false }');
100
+
101
+ process.exit(1);
102
+ }
103
+ }
104
+
105
+ // Run if called directly
106
+ if (require.main === module) {
107
+ runDiscovery();
108
+ }
109
+
110
+ module.exports = { runDiscovery };
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs-extra');
4
+ const path = require('path');
5
+
6
+ const PROJECT_ROOT = process.cwd();
7
+ const LAYER_PATH = path.join(PROJECT_ROOT, 'layers/prisma/nodejs/node_modules');
8
+
9
+ async function verifyLayerStructure() {
10
+ console.log('Verifying Prisma layer structure...\n');
11
+
12
+ const checks = [
13
+ {
14
+ name: 'PostgreSQL schema',
15
+ path: 'generated/prisma-postgresql/schema.prisma'
16
+ },
17
+ {
18
+ name: 'PostgreSQL migrations directory',
19
+ path: 'generated/prisma-postgresql/migrations'
20
+ },
21
+ {
22
+ name: 'PostgreSQL migration_lock.toml',
23
+ path: 'generated/prisma-postgresql/migrations/migration_lock.toml'
24
+ },
25
+ {
26
+ name: '@prisma/client runtime',
27
+ path: '@prisma/client/runtime'
28
+ }
29
+ ];
30
+
31
+ let allPassed = true;
32
+
33
+ for (const check of checks) {
34
+ const fullPath = path.join(LAYER_PATH, check.path);
35
+ const exists = await fs.pathExists(fullPath);
36
+
37
+ if (exists) {
38
+ console.log(`✓ ${check.name}`);
39
+ } else {
40
+ console.log(`✗ ${check.name} (missing)`);
41
+ allPassed = false;
42
+ }
43
+ }
44
+
45
+ console.log('\n');
46
+
47
+ if (allPassed) {
48
+ console.log('✓ All checks passed!');
49
+ const migrationsPath = path.join(LAYER_PATH, 'generated/prisma-postgresql/migrations');
50
+ const migrationFiles = await fs.readdir(migrationsPath);
51
+ console.log(`\nFound ${migrationFiles.length} items in migrations directory:`);
52
+ migrationFiles.forEach(file => {
53
+ console.log(` - ${file}`);
54
+ });
55
+ return 0;
56
+ } else {
57
+ console.log('✗ Some checks failed!');
58
+ return 1;
59
+ }
60
+ }
61
+
62
+ if (require.main === module) {
63
+ verifyLayerStructure()
64
+ .then(code => process.exit(code))
65
+ .catch(err => {
66
+ console.error('Error:', err.message);
67
+ process.exit(1);
68
+ });
69
+ }
70
+
71
+ module.exports = { verifyLayerStructure };
72
+
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@friggframework/devtools",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0-next.62",
4
+ "version": "2.0.0-next.63",
5
5
  "bin": {
6
6
  "frigg": "./frigg-cli/index.js"
7
7
  },
8
8
  "files": [
9
9
  "frigg-cli/",
10
+ "infrastructure/",
10
11
  "migrations/",
11
12
  "management-ui/dist/",
12
13
  "index.js",
@@ -23,9 +24,9 @@
23
24
  "@babel/eslint-parser": "^7.18.9",
24
25
  "@babel/parser": "^7.25.3",
25
26
  "@babel/traverse": "^7.25.3",
26
- "@friggframework/core": "2.0.0-next.62",
27
- "@friggframework/schemas": "2.0.0-next.62",
28
- "@friggframework/test": "2.0.0-next.62",
27
+ "@friggframework/core": "2.0.0-next.63",
28
+ "@friggframework/schemas": "2.0.0-next.63",
29
+ "@friggframework/test": "2.0.0-next.63",
29
30
  "@hapi/boom": "^10.0.1",
30
31
  "@inquirer/prompts": "^5.3.8",
31
32
  "axios": "^1.7.2",
@@ -53,8 +54,8 @@
53
54
  "validate-npm-package-name": "^5.0.0"
54
55
  },
55
56
  "devDependencies": {
56
- "@friggframework/eslint-config": "2.0.0-next.62",
57
- "@friggframework/prettier-config": "2.0.0-next.62",
57
+ "@friggframework/eslint-config": "2.0.0-next.63",
58
+ "@friggframework/prettier-config": "2.0.0-next.63",
58
59
  "aws-sdk-client-mock": "^4.1.0",
59
60
  "aws-sdk-client-mock-jest": "^4.1.0",
60
61
  "jest": "^30.1.3",
@@ -86,5 +87,5 @@
86
87
  "publishConfig": {
87
88
  "access": "public"
88
89
  },
89
- "gitHead": "adce695c05836d713b88b959f1eae62c0a7ce729"
90
+ "gitHead": "a24ce0747f119b26e911ae821cd20997b9473c61"
90
91
  }