@friggframework/devtools 2.0.0-next.4 → 2.0.0-next.40

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 (197) hide show
  1. package/frigg-cli/.eslintrc.js +141 -0
  2. package/frigg-cli/__tests__/jest.config.js +102 -0
  3. package/frigg-cli/__tests__/unit/commands/build.test.js +483 -0
  4. package/frigg-cli/__tests__/unit/commands/install.test.js +418 -0
  5. package/frigg-cli/__tests__/unit/commands/ui.test.js +592 -0
  6. package/frigg-cli/__tests__/utils/command-tester.js +170 -0
  7. package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
  8. package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
  9. package/frigg-cli/__tests__/utils/test-setup.js +286 -0
  10. package/frigg-cli/build-command/index.js +54 -0
  11. package/frigg-cli/deploy-command/index.js +175 -0
  12. package/frigg-cli/generate-command/__tests__/generate-command.test.js +312 -0
  13. package/frigg-cli/generate-command/azure-generator.js +43 -0
  14. package/frigg-cli/generate-command/gcp-generator.js +47 -0
  15. package/frigg-cli/generate-command/index.js +332 -0
  16. package/frigg-cli/generate-command/terraform-generator.js +555 -0
  17. package/frigg-cli/generate-iam-command.js +115 -0
  18. package/frigg-cli/index.js +47 -1
  19. package/frigg-cli/index.test.js +1 -4
  20. package/frigg-cli/init-command/backend-first-handler.js +756 -0
  21. package/frigg-cli/init-command/index.js +93 -0
  22. package/frigg-cli/init-command/template-handler.js +143 -0
  23. package/frigg-cli/install-command/index.js +1 -4
  24. package/frigg-cli/package.json +51 -0
  25. package/frigg-cli/start-command/index.js +24 -4
  26. package/frigg-cli/test/init-command.test.js +180 -0
  27. package/frigg-cli/test/npm-registry.test.js +319 -0
  28. package/frigg-cli/ui-command/index.js +154 -0
  29. package/frigg-cli/utils/app-resolver.js +319 -0
  30. package/frigg-cli/utils/backend-path.js +16 -17
  31. package/frigg-cli/utils/npm-registry.js +167 -0
  32. package/frigg-cli/utils/process-manager.js +199 -0
  33. package/frigg-cli/utils/repo-detection.js +405 -0
  34. package/infrastructure/DEPLOYMENT-INSTRUCTIONS.md +268 -0
  35. package/infrastructure/GENERATE-IAM-DOCS.md +278 -0
  36. package/infrastructure/IAM-POLICY-TEMPLATES.md +176 -0
  37. package/infrastructure/README.md +443 -0
  38. package/infrastructure/WEBSOCKET-CONFIGURATION.md +105 -0
  39. package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
  40. package/infrastructure/__tests__/helpers/test-utils.js +277 -0
  41. package/infrastructure/aws-discovery.js +1176 -0
  42. package/infrastructure/aws-discovery.test.js +1220 -0
  43. package/infrastructure/build-time-discovery.js +206 -0
  44. package/infrastructure/build-time-discovery.test.js +378 -0
  45. package/infrastructure/create-frigg-infrastructure.js +3 -5
  46. package/infrastructure/env-validator.js +77 -0
  47. package/infrastructure/frigg-deployment-iam-stack.yaml +401 -0
  48. package/infrastructure/iam-generator.js +836 -0
  49. package/infrastructure/iam-generator.test.js +172 -0
  50. package/infrastructure/iam-policy-basic.json +218 -0
  51. package/infrastructure/iam-policy-full.json +288 -0
  52. package/infrastructure/integration.test.js +383 -0
  53. package/infrastructure/run-discovery.js +110 -0
  54. package/infrastructure/serverless-template.js +1472 -138
  55. package/infrastructure/serverless-template.test.js +1759 -0
  56. package/management-ui/.eslintrc.js +22 -0
  57. package/management-ui/README.md +203 -0
  58. package/management-ui/components.json +21 -0
  59. package/management-ui/docs/phase2-integration-guide.md +320 -0
  60. package/management-ui/index.html +13 -0
  61. package/management-ui/package-lock.json +16517 -0
  62. package/management-ui/package.json +76 -0
  63. package/management-ui/packages/devtools/frigg-cli/ui-command/index.js +302 -0
  64. package/management-ui/postcss.config.js +6 -0
  65. package/management-ui/server/api/backend.js +256 -0
  66. package/management-ui/server/api/cli.js +315 -0
  67. package/management-ui/server/api/codegen.js +663 -0
  68. package/management-ui/server/api/connections.js +857 -0
  69. package/management-ui/server/api/discovery.js +185 -0
  70. package/management-ui/server/api/environment/index.js +1 -0
  71. package/management-ui/server/api/environment/router.js +378 -0
  72. package/management-ui/server/api/environment.js +328 -0
  73. package/management-ui/server/api/integrations.js +876 -0
  74. package/management-ui/server/api/logs.js +248 -0
  75. package/management-ui/server/api/monitoring.js +282 -0
  76. package/management-ui/server/api/open-ide.js +31 -0
  77. package/management-ui/server/api/project.js +1029 -0
  78. package/management-ui/server/api/users/sessions.js +371 -0
  79. package/management-ui/server/api/users/simulation.js +254 -0
  80. package/management-ui/server/api/users.js +362 -0
  81. package/management-ui/server/api-contract.md +275 -0
  82. package/management-ui/server/index.js +873 -0
  83. package/management-ui/server/middleware/errorHandler.js +93 -0
  84. package/management-ui/server/middleware/security.js +32 -0
  85. package/management-ui/server/processManager.js +296 -0
  86. package/management-ui/server/server.js +346 -0
  87. package/management-ui/server/services/aws-monitor.js +413 -0
  88. package/management-ui/server/services/npm-registry.js +347 -0
  89. package/management-ui/server/services/template-engine.js +538 -0
  90. package/management-ui/server/utils/cliIntegration.js +220 -0
  91. package/management-ui/server/utils/environment/auditLogger.js +471 -0
  92. package/management-ui/server/utils/environment/awsParameterStore.js +264 -0
  93. package/management-ui/server/utils/environment/encryption.js +278 -0
  94. package/management-ui/server/utils/environment/envFileManager.js +286 -0
  95. package/management-ui/server/utils/import-commonjs.js +28 -0
  96. package/management-ui/server/utils/response.js +83 -0
  97. package/management-ui/server/websocket/handler.js +325 -0
  98. package/management-ui/src/App.jsx +109 -0
  99. package/management-ui/src/assets/FriggLogo.svg +1 -0
  100. package/management-ui/src/components/AppRouter.jsx +65 -0
  101. package/management-ui/src/components/Button.jsx +70 -0
  102. package/management-ui/src/components/Card.jsx +97 -0
  103. package/management-ui/src/components/EnvironmentCompare.jsx +400 -0
  104. package/management-ui/src/components/EnvironmentEditor.jsx +372 -0
  105. package/management-ui/src/components/EnvironmentImportExport.jsx +469 -0
  106. package/management-ui/src/components/EnvironmentSchema.jsx +491 -0
  107. package/management-ui/src/components/EnvironmentSecurity.jsx +463 -0
  108. package/management-ui/src/components/ErrorBoundary.jsx +73 -0
  109. package/management-ui/src/components/IntegrationCard.jsx +481 -0
  110. package/management-ui/src/components/IntegrationCardEnhanced.jsx +770 -0
  111. package/management-ui/src/components/IntegrationExplorer.jsx +379 -0
  112. package/management-ui/src/components/IntegrationStatus.jsx +336 -0
  113. package/management-ui/src/components/Layout.jsx +716 -0
  114. package/management-ui/src/components/LoadingSpinner.jsx +113 -0
  115. package/management-ui/src/components/RepositoryPicker.jsx +248 -0
  116. package/management-ui/src/components/SessionMonitor.jsx +350 -0
  117. package/management-ui/src/components/StatusBadge.jsx +208 -0
  118. package/management-ui/src/components/UserContextSwitcher.jsx +212 -0
  119. package/management-ui/src/components/UserSimulation.jsx +327 -0
  120. package/management-ui/src/components/Welcome.jsx +434 -0
  121. package/management-ui/src/components/codegen/APIEndpointGenerator.jsx +637 -0
  122. package/management-ui/src/components/codegen/APIModuleSelector.jsx +227 -0
  123. package/management-ui/src/components/codegen/CodeGenerationWizard.jsx +247 -0
  124. package/management-ui/src/components/codegen/CodePreviewEditor.jsx +316 -0
  125. package/management-ui/src/components/codegen/DynamicModuleForm.jsx +271 -0
  126. package/management-ui/src/components/codegen/FormBuilder.jsx +737 -0
  127. package/management-ui/src/components/codegen/IntegrationGenerator.jsx +855 -0
  128. package/management-ui/src/components/codegen/ProjectScaffoldWizard.jsx +797 -0
  129. package/management-ui/src/components/codegen/SchemaBuilder.jsx +303 -0
  130. package/management-ui/src/components/codegen/TemplateSelector.jsx +586 -0
  131. package/management-ui/src/components/codegen/index.js +10 -0
  132. package/management-ui/src/components/connections/ConnectionConfigForm.jsx +362 -0
  133. package/management-ui/src/components/connections/ConnectionHealthMonitor.jsx +182 -0
  134. package/management-ui/src/components/connections/ConnectionTester.jsx +200 -0
  135. package/management-ui/src/components/connections/EntityRelationshipMapper.jsx +292 -0
  136. package/management-ui/src/components/connections/OAuthFlow.jsx +204 -0
  137. package/management-ui/src/components/connections/index.js +5 -0
  138. package/management-ui/src/components/index.js +21 -0
  139. package/management-ui/src/components/monitoring/APIGatewayMetrics.jsx +222 -0
  140. package/management-ui/src/components/monitoring/LambdaMetrics.jsx +169 -0
  141. package/management-ui/src/components/monitoring/MetricsChart.jsx +197 -0
  142. package/management-ui/src/components/monitoring/MonitoringDashboard.jsx +393 -0
  143. package/management-ui/src/components/monitoring/SQSMetrics.jsx +246 -0
  144. package/management-ui/src/components/monitoring/index.js +6 -0
  145. package/management-ui/src/components/monitoring/monitoring.css +218 -0
  146. package/management-ui/src/components/theme-provider.jsx +52 -0
  147. package/management-ui/src/components/theme-toggle.jsx +39 -0
  148. package/management-ui/src/components/ui/badge.tsx +36 -0
  149. package/management-ui/src/components/ui/button.test.jsx +56 -0
  150. package/management-ui/src/components/ui/button.tsx +57 -0
  151. package/management-ui/src/components/ui/card.tsx +76 -0
  152. package/management-ui/src/components/ui/dropdown-menu.tsx +199 -0
  153. package/management-ui/src/components/ui/select.tsx +157 -0
  154. package/management-ui/src/components/ui/skeleton.jsx +15 -0
  155. package/management-ui/src/hooks/useFrigg.jsx +601 -0
  156. package/management-ui/src/hooks/useSocket.jsx +58 -0
  157. package/management-ui/src/index.css +193 -0
  158. package/management-ui/src/lib/utils.ts +6 -0
  159. package/management-ui/src/main.jsx +10 -0
  160. package/management-ui/src/pages/CodeGeneration.jsx +14 -0
  161. package/management-ui/src/pages/Connections.jsx +252 -0
  162. package/management-ui/src/pages/ConnectionsEnhanced.jsx +633 -0
  163. package/management-ui/src/pages/Dashboard.jsx +311 -0
  164. package/management-ui/src/pages/Environment.jsx +314 -0
  165. package/management-ui/src/pages/IntegrationConfigure.jsx +669 -0
  166. package/management-ui/src/pages/IntegrationDiscovery.jsx +567 -0
  167. package/management-ui/src/pages/IntegrationTest.jsx +742 -0
  168. package/management-ui/src/pages/Integrations.jsx +253 -0
  169. package/management-ui/src/pages/Monitoring.jsx +17 -0
  170. package/management-ui/src/pages/Simulation.jsx +155 -0
  171. package/management-ui/src/pages/Users.jsx +492 -0
  172. package/management-ui/src/services/api.js +41 -0
  173. package/management-ui/src/services/apiModuleService.js +193 -0
  174. package/management-ui/src/services/websocket-handlers.js +120 -0
  175. package/management-ui/src/test/api/project.test.js +273 -0
  176. package/management-ui/src/test/components/Welcome.test.jsx +378 -0
  177. package/management-ui/src/test/mocks/server.js +178 -0
  178. package/management-ui/src/test/setup.js +61 -0
  179. package/management-ui/src/test/utils/test-utils.jsx +134 -0
  180. package/management-ui/src/utils/repository.js +98 -0
  181. package/management-ui/src/utils/repository.test.js +118 -0
  182. package/management-ui/src/workflows/phase2-integration-workflows.js +884 -0
  183. package/management-ui/tailwind.config.js +63 -0
  184. package/management-ui/tsconfig.json +37 -0
  185. package/management-ui/tsconfig.node.json +10 -0
  186. package/management-ui/vite.config.js +26 -0
  187. package/management-ui/vitest.config.js +38 -0
  188. package/package.json +20 -9
  189. package/infrastructure/app-handler-helpers.js +0 -57
  190. package/infrastructure/backend-utils.js +0 -90
  191. package/infrastructure/routers/auth.js +0 -26
  192. package/infrastructure/routers/integration-defined-routers.js +0 -37
  193. package/infrastructure/routers/middleware/loadUser.js +0 -15
  194. package/infrastructure/routers/middleware/requireLoggedInUser.js +0 -12
  195. package/infrastructure/routers/user.js +0 -41
  196. package/infrastructure/routers/websocket.js +0 -55
  197. package/infrastructure/workers/integration-defined-workers.js +0 -24
@@ -0,0 +1,383 @@
1
+ const fs = require('fs');
2
+ const { composeServerlessDefinition } = require('./serverless-template');
3
+ const { AWSDiscovery } = require('./aws-discovery');
4
+ const { BuildTimeDiscovery } = require('./build-time-discovery');
5
+ const FriggServerlessPlugin = require('../../serverless-plugin/index');
6
+
7
+ // Integration tests for end-to-end AWS discovery and serverless config generation
8
+ describe('VPC/KMS/SSM Integration Tests', () => {
9
+ let mockAWSDiscovery;
10
+ let buildTimeDiscovery;
11
+
12
+ const mockAWSResources = {
13
+ defaultVpcId: 'vpc-12345678',
14
+ defaultSecurityGroupId: 'sg-12345678',
15
+ privateSubnetId1: 'subnet-private-1',
16
+ privateSubnetId2: 'subnet-private-2',
17
+ privateRouteTableId: 'rtb-12345678',
18
+ defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
19
+ };
20
+
21
+ beforeEach(() => {
22
+ // Mock AWSDiscovery to return consistent test data
23
+ mockAWSDiscovery = {
24
+ discoverResources: jest.fn().mockResolvedValue(mockAWSResources),
25
+ findDefaultVpc: jest.fn().mockResolvedValue({ VpcId: mockAWSResources.defaultVpcId }),
26
+ findPrivateSubnets: jest.fn().mockResolvedValue([
27
+ { SubnetId: mockAWSResources.privateSubnetId1 },
28
+ { SubnetId: mockAWSResources.privateSubnetId2 }
29
+ ]),
30
+ findDefaultSecurityGroup: jest.fn().mockResolvedValue({ GroupId: mockAWSResources.defaultSecurityGroupId }),
31
+ findPrivateRouteTable: jest.fn().mockResolvedValue({ RouteTableId: mockAWSResources.privateRouteTableId }),
32
+ findDefaultKmsKey: jest.fn().mockResolvedValue(mockAWSResources.defaultKmsKeyId)
33
+ };
34
+
35
+ jest.doMock('./aws-discovery', () => ({
36
+ AWSDiscovery: jest.fn(() => mockAWSDiscovery)
37
+ }));
38
+
39
+ buildTimeDiscovery = new BuildTimeDiscovery('us-east-1');
40
+ });
41
+
42
+ afterEach(() => {
43
+ jest.clearAllMocks();
44
+ jest.resetModules();
45
+ });
46
+
47
+ describe('End-to-End Serverless Configuration Generation', () => {
48
+ it('should generate complete serverless config with VPC, KMS, and SSM enabled', async () => {
49
+ const appDefinition = {
50
+ name: 'test-frigg-app',
51
+ vpc: { enable: true },
52
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
53
+ ssm: { enable: true },
54
+ integrations: [{
55
+ Definition: {
56
+ name: 'testIntegration'
57
+ }
58
+ }]
59
+ };
60
+
61
+ // Run AWS discovery
62
+ const discoveredResources = await mockAWSDiscovery.discoverResources();
63
+
64
+ // Set environment variables as would happen in build
65
+ process.env.AWS_DISCOVERY_VPC_ID = discoveredResources.defaultVpcId;
66
+ process.env.AWS_DISCOVERY_SECURITY_GROUP_ID = discoveredResources.defaultSecurityGroupId;
67
+ process.env.AWS_DISCOVERY_SUBNET_ID_1 = discoveredResources.privateSubnetId1;
68
+ process.env.AWS_DISCOVERY_SUBNET_ID_2 = discoveredResources.privateSubnetId2;
69
+ process.env.AWS_DISCOVERY_ROUTE_TABLE_ID = discoveredResources.privateRouteTableId;
70
+ process.env.AWS_DISCOVERY_KMS_KEY_ID =discoveredResources.defaultKmsKeyId;
71
+
72
+ // Generate serverless configuration
73
+ const serverlessConfig = composeServerlessDefinition(appDefinition);
74
+
75
+ // Verify VPC configuration
76
+ expect(serverlessConfig.provider.vpc).toBe('${self:custom.vpc.${self:provider.stage}}');
77
+ expect(serverlessConfig.custom.vpc).toEqual({
78
+ '${self:provider.stage}': {
79
+ securityGroupIds: ['${env:AWS_DISCOVERY_SECURITY_GROUP_ID}'],
80
+ subnetIds: [
81
+ '${env:AWS_DISCOVERY_SUBNET_ID_1}',
82
+ '${env:AWS_DISCOVERY_SUBNET_ID_2}'
83
+ ]
84
+ }
85
+ });
86
+
87
+ // Verify VPC Endpoint
88
+ expect(serverlessConfig.resources.Resources.VPCEndpointS3).toEqual({
89
+ Type: 'AWS::EC2::VPCEndpoint',
90
+ Properties: {
91
+ VpcId: '${env:AWS_DISCOVERY_VPC_ID}',
92
+ ServiceName: 'com.amazonaws.${self:provider.region}.s3',
93
+ VpcEndpointType: 'Gateway',
94
+ RouteTableIds: ['${env:AWS_DISCOVERY_ROUTE_TABLE_ID}']
95
+ }
96
+ });
97
+
98
+ // Verify KMS configuration
99
+ expect(serverlessConfig.plugins).toContain('serverless-kms-grants');
100
+ expect(serverlessConfig.provider.environment.KMS_KEY_ARN).toBe('${self:custom.kmsGrants.kmsKeyId}');
101
+ expect(serverlessConfig.custom.kmsGrants).toEqual({
102
+ kmsKeyId: '${env:AWS_DISCOVERY_KMS_KEY_ID}'
103
+ });
104
+
105
+ // Verify KMS IAM permissions
106
+ const kmsPermission = serverlessConfig.provider.iamRoleStatements.find(
107
+ statement => statement.Action.includes('kms:GenerateDataKey')
108
+ );
109
+ expect(kmsPermission).toBeDefined();
110
+
111
+ // Verify SSM configuration
112
+ expect(serverlessConfig.provider.layers).toEqual([
113
+ 'arn:aws:lambda:${self:provider.region}:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11'
114
+ ]);
115
+ expect(serverlessConfig.provider.environment.SSM_PARAMETER_PREFIX).toBe('/${self:service}/${self:provider.stage}');
116
+
117
+ // Verify SSM IAM permissions
118
+ const ssmPermission = serverlessConfig.provider.iamRoleStatements.find(
119
+ statement => statement.Action.includes('ssm:GetParameter')
120
+ );
121
+ expect(ssmPermission).toBeDefined();
122
+
123
+ // Verify integration resources
124
+ expect(serverlessConfig.functions.testIntegration).toBeDefined();
125
+ expect(serverlessConfig.functions.testIntegrationQueueWorker).toBeDefined();
126
+ expect(serverlessConfig.resources.Resources.TestIntegrationQueue).toBeDefined();
127
+
128
+ // Clean up environment
129
+ delete process.env.AWS_DISCOVERY_VPC_ID;
130
+ delete process.env.AWS_DISCOVERY_SECURITY_GROUP_ID;
131
+ delete process.env.AWS_DISCOVERY_SUBNET_ID_1;
132
+ delete process.env.AWS_DISCOVERY_SUBNET_ID_2;
133
+ delete process.env.AWS_DISCOVERY_ROUTE_TABLE_ID;
134
+ delete process.env.AWS_DISCOVERY_KMS_KEY_ID;
135
+ });
136
+
137
+ it('should generate config with only VPC enabled', async () => {
138
+ const appDefinition = {
139
+ name: 'vpc-only-app',
140
+ vpc: { enable: true },
141
+ integrations: []
142
+ };
143
+
144
+ process.env.AWS_DISCOVERY_VPC_ID = mockAWSResources.defaultVpcId;
145
+ process.env.AWS_DISCOVERY_SECURITY_GROUP_ID = mockAWSResources.defaultSecurityGroupId;
146
+ process.env.AWS_DISCOVERY_SUBNET_ID_1 = mockAWSResources.privateSubnetId1;
147
+ process.env.AWS_DISCOVERY_SUBNET_ID_2 = mockAWSResources.privateSubnetId2;
148
+ process.env.AWS_DISCOVERY_ROUTE_TABLE_ID = mockAWSResources.privateRouteTableId;
149
+
150
+ const serverlessConfig = composeServerlessDefinition(appDefinition);
151
+
152
+ // Should have VPC config
153
+ expect(serverlessConfig.provider.vpc).toBeDefined();
154
+ expect(serverlessConfig.custom.vpc).toBeDefined();
155
+ expect(serverlessConfig.resources.Resources.VPCEndpointS3).toBeDefined();
156
+
157
+ // Should not have KMS config
158
+ expect(serverlessConfig.plugins).not.toContain('serverless-kms-grants');
159
+ expect(serverlessConfig.provider.environment.KMS_KEY_ARN).toBeUndefined();
160
+
161
+ // Should not have SSM config
162
+ expect(serverlessConfig.provider.layers).toBeUndefined();
163
+ expect(serverlessConfig.provider.environment.SSM_PARAMETER_PREFIX).toBeUndefined();
164
+
165
+ // Clean up
166
+ delete process.env.AWS_DISCOVERY_VPC_ID;
167
+ delete process.env.AWS_DISCOVERY_SECURITY_GROUP_ID;
168
+ delete process.env.AWS_DISCOVERY_SUBNET_ID_1;
169
+ delete process.env.AWS_DISCOVERY_SUBNET_ID_2;
170
+ delete process.env.AWS_DISCOVERY_ROUTE_TABLE_ID;
171
+ });
172
+
173
+ it('should generate config with only KMS enabled', async () => {
174
+ const appDefinition = {
175
+ name: 'kms-only-app',
176
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
177
+ integrations: []
178
+ };
179
+
180
+ process.env.AWS_DISCOVERY_KMS_KEY_ID =mockAWSResources.defaultKmsKeyId;
181
+
182
+ const serverlessConfig = composeServerlessDefinition(appDefinition);
183
+
184
+ // Should have KMS config
185
+ expect(serverlessConfig.plugins).toContain('serverless-kms-grants');
186
+ expect(serverlessConfig.custom.kmsGrants).toBeDefined();
187
+
188
+ // Should not have VPC config
189
+ expect(serverlessConfig.provider.vpc).toBeUndefined();
190
+
191
+ // Should not have SSM config
192
+ expect(serverlessConfig.provider.layers).toBeUndefined();
193
+
194
+ delete process.env.AWS_DISCOVERY_KMS_KEY_ID;
195
+ });
196
+ });
197
+
198
+ describe('Plugin Integration', () => {
199
+ it('should trigger AWS discovery through serverless plugin', async () => {
200
+ const mockServerless = {
201
+ cli: { log: jest.fn() },
202
+ service: {
203
+ provider: {
204
+ name: 'aws',
205
+ region: 'us-east-1',
206
+ vpc: '${self:custom.vpc.${self:provider.stage}}'
207
+ },
208
+ plugins: ['serverless-kms-grants'],
209
+ custom: {},
210
+ functions: {}
211
+ },
212
+ processedInput: { commands: [] },
213
+ getProvider: jest.fn(() => ({})),
214
+ extendConfiguration: jest.fn()
215
+ };
216
+
217
+ const plugin = new FriggServerlessPlugin(mockServerless, { stage: 'test' });
218
+
219
+ // Mock BuildTimeDiscovery
220
+ const mockBuildTimeDiscovery = {
221
+ preBuildHook: jest.fn().mockResolvedValue(mockAWSResources)
222
+ };
223
+
224
+ jest.doMock('./build-time-discovery', () => ({
225
+ BuildTimeDiscovery: jest.fn(() => mockBuildTimeDiscovery)
226
+ }));
227
+
228
+ // Test the beforePackageInitialize hook
229
+ await plugin.beforePackageInitialize();
230
+
231
+ expect(mockBuildTimeDiscovery.preBuildHook).toHaveBeenCalledWith(
232
+ expect.objectContaining({
233
+ vpc: { enable: true },
234
+ encryption: { fieldLevelEncryptionMethod: 'kms' }
235
+ }),
236
+ 'us-east-1'
237
+ );
238
+
239
+ expect(mockServerless.cli.log).toHaveBeenCalledWith('AWS discovery completed successfully');
240
+ });
241
+
242
+ it('should handle plugin discovery failure gracefully', async () => {
243
+ const mockServerless = {
244
+ cli: { log: jest.fn() },
245
+ service: {
246
+ provider: {
247
+ name: 'aws',
248
+ region: 'us-east-1',
249
+ vpc: '${self:custom.vpc}'
250
+ },
251
+ plugins: [],
252
+ custom: {},
253
+ functions: {}
254
+ },
255
+ processedInput: { commands: [] },
256
+ getProvider: jest.fn(() => ({})),
257
+ extendConfiguration: jest.fn()
258
+ };
259
+
260
+ const plugin = new FriggServerlessPlugin(mockServerless, { stage: 'test' });
261
+
262
+ // Mock BuildTimeDiscovery to fail
263
+ const mockBuildTimeDiscovery = {
264
+ preBuildHook: jest.fn().mockRejectedValue(new Error('AWS API Error'))
265
+ };
266
+
267
+ jest.doMock('./build-time-discovery', () => ({
268
+ BuildTimeDiscovery: jest.fn(() => mockBuildTimeDiscovery)
269
+ }));
270
+
271
+ await plugin.beforePackageInitialize();
272
+
273
+ expect(mockServerless.cli.log).toHaveBeenCalledWith('AWS discovery failed, continuing with deployment...');
274
+ expect(mockServerless.cli.log).toHaveBeenCalledWith('Using fallback values for AWS resources');
275
+
276
+ // Verify fallback values are set
277
+ expect(process.env.AWS_DISCOVERY_VPC_ID).toBe('vpc-fallback');
278
+ expect(process.env.AWS_DISCOVERY_KMS_KEY_ID).toBe('arn:aws:kms:*:*:key/*');
279
+ });
280
+ });
281
+
282
+ describe('Template Variable Replacement', () => {
283
+ it('should replace environment variable placeholders with actual values', () => {
284
+ const template = `
285
+ provider:
286
+ vpc:
287
+ securityGroupIds:
288
+ - \${env:AWS_DISCOVERY_SECURITY_GROUP_ID}
289
+ subnetIds:
290
+ - \${env:AWS_DISCOVERY_SUBNET_ID_1}
291
+ - \${env:AWS_DISCOVERY_SUBNET_ID_2}
292
+ environment:
293
+ KMS_KEY_ARN: \${env:AWS_DISCOVERY_KMS_KEY_ID}
294
+ resources:
295
+ VPCEndpoint:
296
+ Properties:
297
+ VpcId: \${env:AWS_DISCOVERY_VPC_ID}
298
+ `;
299
+
300
+ // Set environment variables
301
+ process.env.AWS_DISCOVERY_VPC_ID = mockAWSResources.defaultVpcId;
302
+ process.env.AWS_DISCOVERY_SECURITY_GROUP_ID = mockAWSResources.defaultSecurityGroupId;
303
+ process.env.AWS_DISCOVERY_SUBNET_ID_1 = mockAWSResources.privateSubnetId1;
304
+ process.env.AWS_DISCOVERY_SUBNET_ID_2 = mockAWSResources.privateSubnetId2;
305
+ process.env.AWS_DISCOVERY_KMS_KEY_ID =mockAWSResources.defaultKmsKeyId;
306
+
307
+ // In a real deployment, serverless framework would resolve these environment variables
308
+ // For testing, we can verify the placeholders are correctly formatted
309
+ expect(template).toContain('${env:AWS_DISCOVERY_VPC_ID}');
310
+ expect(template).toContain('${env:AWS_DISCOVERY_SECURITY_GROUP_ID}');
311
+ expect(template).toContain('${env:AWS_DISCOVERY_SUBNET_ID_1}');
312
+ expect(template).toContain('${env:AWS_DISCOVERY_SUBNET_ID_2}');
313
+ expect(template).toContain('${env:AWS_DISCOVERY_KMS_KEY_ID}');
314
+
315
+ // Clean up
316
+ delete process.env.AWS_DISCOVERY_VPC_ID;
317
+ delete process.env.AWS_DISCOVERY_SECURITY_GROUP_ID;
318
+ delete process.env.AWS_DISCOVERY_SUBNET_ID_1;
319
+ delete process.env.AWS_DISCOVERY_SUBNET_ID_2;
320
+ delete process.env.AWS_DISCOVERY_KMS_KEY_ID;
321
+ });
322
+ });
323
+
324
+ describe('Error Scenarios', () => {
325
+ it('should handle AWS discovery timeout gracefully', async () => {
326
+ const mockFailingDiscovery = {
327
+ discoverResources: jest.fn().mockRejectedValue(new Error('Request timeout'))
328
+ };
329
+
330
+ jest.doMock('./aws-discovery', () => ({
331
+ AWSDiscovery: jest.fn(() => mockFailingDiscovery)
332
+ }));
333
+
334
+ const appDefinition = {
335
+ vpc: { enable: true },
336
+ integrations: []
337
+ };
338
+
339
+ await expect(buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1')).rejects.toThrow('Request timeout');
340
+ });
341
+
342
+ it('should handle partial AWS resource discovery', async () => {
343
+ const partialResources = {
344
+ defaultVpcId: 'vpc-12345678',
345
+ defaultSecurityGroupId: 'sg-12345678',
346
+ privateSubnetId1: 'subnet-1',
347
+ privateSubnetId2: 'subnet-1', // Same subnet used twice
348
+ privateRouteTableId: 'rtb-12345678',
349
+ defaultKmsKeyId: '*' // Fallback KMS key
350
+ };
351
+
352
+ mockAWSDiscovery.discoverResources.mockResolvedValue(partialResources);
353
+
354
+ const appDefinition = {
355
+ vpc: { enable: true },
356
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
357
+ integrations: []
358
+ };
359
+
360
+ const result = await buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1');
361
+
362
+ expect(result).toEqual(partialResources);
363
+ expect(result.privateSubnetId2).toBe(result.privateSubnetId1); // Should handle single subnet scenario
364
+ expect(result.defaultKmsKeyId).toBe('*'); // Should handle KMS fallback
365
+ });
366
+ });
367
+
368
+ describe('Multi-Region Support', () => {
369
+ it('should support different AWS regions', async () => {
370
+ const appDefinition = {
371
+ vpc: { enable: true },
372
+ integrations: []
373
+ };
374
+
375
+ const euWestDiscovery = new BuildTimeDiscovery('eu-west-1');
376
+
377
+ await euWestDiscovery.preBuildHook(appDefinition, 'eu-west-1');
378
+
379
+ // Verify that AWSDiscovery was instantiated with correct region
380
+ expect(mockAWSDiscovery.discoverResources).toHaveBeenCalled();
381
+ });
382
+ });
383
+ });
@@ -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 };