@friggframework/devtools 2.0.0-next.3 → 2.0.0-next.30

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 (199) 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 +36 -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/AWS-DISCOVERY-TROUBLESHOOTING.md +245 -0
  35. package/infrastructure/AWS-IAM-CREDENTIAL-NEEDS.md +596 -0
  36. package/infrastructure/DEPLOYMENT-INSTRUCTIONS.md +268 -0
  37. package/infrastructure/GENERATE-IAM-DOCS.md +253 -0
  38. package/infrastructure/IAM-POLICY-TEMPLATES.md +176 -0
  39. package/infrastructure/README-TESTING.md +332 -0
  40. package/infrastructure/README.md +421 -0
  41. package/infrastructure/WEBSOCKET-CONFIGURATION.md +105 -0
  42. package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
  43. package/infrastructure/__tests__/helpers/test-utils.js +277 -0
  44. package/infrastructure/aws-discovery.js +568 -0
  45. package/infrastructure/aws-discovery.test.js +373 -0
  46. package/infrastructure/build-time-discovery.js +206 -0
  47. package/infrastructure/build-time-discovery.test.js +375 -0
  48. package/infrastructure/create-frigg-infrastructure.js +3 -5
  49. package/infrastructure/frigg-deployment-iam-stack.yaml +379 -0
  50. package/infrastructure/iam-generator.js +687 -0
  51. package/infrastructure/iam-generator.test.js +169 -0
  52. package/infrastructure/iam-policy-basic.json +212 -0
  53. package/infrastructure/iam-policy-full.json +282 -0
  54. package/infrastructure/integration.test.js +383 -0
  55. package/infrastructure/run-discovery.js +110 -0
  56. package/infrastructure/serverless-template.js +923 -113
  57. package/infrastructure/serverless-template.test.js +541 -0
  58. package/management-ui/.eslintrc.js +22 -0
  59. package/management-ui/README.md +203 -0
  60. package/management-ui/components.json +21 -0
  61. package/management-ui/docs/phase2-integration-guide.md +320 -0
  62. package/management-ui/index.html +13 -0
  63. package/management-ui/package-lock.json +16517 -0
  64. package/management-ui/package.json +76 -0
  65. package/management-ui/packages/devtools/frigg-cli/ui-command/index.js +302 -0
  66. package/management-ui/postcss.config.js +6 -0
  67. package/management-ui/server/api/backend.js +256 -0
  68. package/management-ui/server/api/cli.js +315 -0
  69. package/management-ui/server/api/codegen.js +663 -0
  70. package/management-ui/server/api/connections.js +857 -0
  71. package/management-ui/server/api/discovery.js +185 -0
  72. package/management-ui/server/api/environment/index.js +1 -0
  73. package/management-ui/server/api/environment/router.js +378 -0
  74. package/management-ui/server/api/environment.js +328 -0
  75. package/management-ui/server/api/integrations.js +876 -0
  76. package/management-ui/server/api/logs.js +248 -0
  77. package/management-ui/server/api/monitoring.js +282 -0
  78. package/management-ui/server/api/open-ide.js +31 -0
  79. package/management-ui/server/api/project.js +1029 -0
  80. package/management-ui/server/api/users/sessions.js +371 -0
  81. package/management-ui/server/api/users/simulation.js +254 -0
  82. package/management-ui/server/api/users.js +362 -0
  83. package/management-ui/server/api-contract.md +275 -0
  84. package/management-ui/server/index.js +873 -0
  85. package/management-ui/server/middleware/errorHandler.js +93 -0
  86. package/management-ui/server/middleware/security.js +32 -0
  87. package/management-ui/server/processManager.js +296 -0
  88. package/management-ui/server/server.js +346 -0
  89. package/management-ui/server/services/aws-monitor.js +413 -0
  90. package/management-ui/server/services/npm-registry.js +347 -0
  91. package/management-ui/server/services/template-engine.js +538 -0
  92. package/management-ui/server/utils/cliIntegration.js +220 -0
  93. package/management-ui/server/utils/environment/auditLogger.js +471 -0
  94. package/management-ui/server/utils/environment/awsParameterStore.js +264 -0
  95. package/management-ui/server/utils/environment/encryption.js +278 -0
  96. package/management-ui/server/utils/environment/envFileManager.js +286 -0
  97. package/management-ui/server/utils/import-commonjs.js +28 -0
  98. package/management-ui/server/utils/response.js +83 -0
  99. package/management-ui/server/websocket/handler.js +325 -0
  100. package/management-ui/src/App.jsx +109 -0
  101. package/management-ui/src/assets/FriggLogo.svg +1 -0
  102. package/management-ui/src/components/AppRouter.jsx +65 -0
  103. package/management-ui/src/components/Button.jsx +70 -0
  104. package/management-ui/src/components/Card.jsx +97 -0
  105. package/management-ui/src/components/EnvironmentCompare.jsx +400 -0
  106. package/management-ui/src/components/EnvironmentEditor.jsx +372 -0
  107. package/management-ui/src/components/EnvironmentImportExport.jsx +469 -0
  108. package/management-ui/src/components/EnvironmentSchema.jsx +491 -0
  109. package/management-ui/src/components/EnvironmentSecurity.jsx +463 -0
  110. package/management-ui/src/components/ErrorBoundary.jsx +73 -0
  111. package/management-ui/src/components/IntegrationCard.jsx +481 -0
  112. package/management-ui/src/components/IntegrationCardEnhanced.jsx +770 -0
  113. package/management-ui/src/components/IntegrationExplorer.jsx +379 -0
  114. package/management-ui/src/components/IntegrationStatus.jsx +336 -0
  115. package/management-ui/src/components/Layout.jsx +716 -0
  116. package/management-ui/src/components/LoadingSpinner.jsx +113 -0
  117. package/management-ui/src/components/RepositoryPicker.jsx +248 -0
  118. package/management-ui/src/components/SessionMonitor.jsx +350 -0
  119. package/management-ui/src/components/StatusBadge.jsx +208 -0
  120. package/management-ui/src/components/UserContextSwitcher.jsx +212 -0
  121. package/management-ui/src/components/UserSimulation.jsx +327 -0
  122. package/management-ui/src/components/Welcome.jsx +434 -0
  123. package/management-ui/src/components/codegen/APIEndpointGenerator.jsx +637 -0
  124. package/management-ui/src/components/codegen/APIModuleSelector.jsx +227 -0
  125. package/management-ui/src/components/codegen/CodeGenerationWizard.jsx +247 -0
  126. package/management-ui/src/components/codegen/CodePreviewEditor.jsx +316 -0
  127. package/management-ui/src/components/codegen/DynamicModuleForm.jsx +271 -0
  128. package/management-ui/src/components/codegen/FormBuilder.jsx +737 -0
  129. package/management-ui/src/components/codegen/IntegrationGenerator.jsx +855 -0
  130. package/management-ui/src/components/codegen/ProjectScaffoldWizard.jsx +797 -0
  131. package/management-ui/src/components/codegen/SchemaBuilder.jsx +303 -0
  132. package/management-ui/src/components/codegen/TemplateSelector.jsx +586 -0
  133. package/management-ui/src/components/codegen/index.js +10 -0
  134. package/management-ui/src/components/connections/ConnectionConfigForm.jsx +362 -0
  135. package/management-ui/src/components/connections/ConnectionHealthMonitor.jsx +182 -0
  136. package/management-ui/src/components/connections/ConnectionTester.jsx +200 -0
  137. package/management-ui/src/components/connections/EntityRelationshipMapper.jsx +292 -0
  138. package/management-ui/src/components/connections/OAuthFlow.jsx +204 -0
  139. package/management-ui/src/components/connections/index.js +5 -0
  140. package/management-ui/src/components/index.js +21 -0
  141. package/management-ui/src/components/monitoring/APIGatewayMetrics.jsx +222 -0
  142. package/management-ui/src/components/monitoring/LambdaMetrics.jsx +169 -0
  143. package/management-ui/src/components/monitoring/MetricsChart.jsx +197 -0
  144. package/management-ui/src/components/monitoring/MonitoringDashboard.jsx +393 -0
  145. package/management-ui/src/components/monitoring/SQSMetrics.jsx +246 -0
  146. package/management-ui/src/components/monitoring/index.js +6 -0
  147. package/management-ui/src/components/monitoring/monitoring.css +218 -0
  148. package/management-ui/src/components/theme-provider.jsx +52 -0
  149. package/management-ui/src/components/theme-toggle.jsx +39 -0
  150. package/management-ui/src/components/ui/badge.tsx +36 -0
  151. package/management-ui/src/components/ui/button.test.jsx +56 -0
  152. package/management-ui/src/components/ui/button.tsx +57 -0
  153. package/management-ui/src/components/ui/card.tsx +76 -0
  154. package/management-ui/src/components/ui/dropdown-menu.tsx +199 -0
  155. package/management-ui/src/components/ui/select.tsx +157 -0
  156. package/management-ui/src/components/ui/skeleton.jsx +15 -0
  157. package/management-ui/src/hooks/useFrigg.jsx +601 -0
  158. package/management-ui/src/hooks/useSocket.jsx +58 -0
  159. package/management-ui/src/index.css +193 -0
  160. package/management-ui/src/lib/utils.ts +6 -0
  161. package/management-ui/src/main.jsx +10 -0
  162. package/management-ui/src/pages/CodeGeneration.jsx +14 -0
  163. package/management-ui/src/pages/Connections.jsx +252 -0
  164. package/management-ui/src/pages/ConnectionsEnhanced.jsx +633 -0
  165. package/management-ui/src/pages/Dashboard.jsx +311 -0
  166. package/management-ui/src/pages/Environment.jsx +314 -0
  167. package/management-ui/src/pages/IntegrationConfigure.jsx +669 -0
  168. package/management-ui/src/pages/IntegrationDiscovery.jsx +567 -0
  169. package/management-ui/src/pages/IntegrationTest.jsx +742 -0
  170. package/management-ui/src/pages/Integrations.jsx +253 -0
  171. package/management-ui/src/pages/Monitoring.jsx +17 -0
  172. package/management-ui/src/pages/Simulation.jsx +155 -0
  173. package/management-ui/src/pages/Users.jsx +492 -0
  174. package/management-ui/src/services/api.js +41 -0
  175. package/management-ui/src/services/apiModuleService.js +193 -0
  176. package/management-ui/src/services/websocket-handlers.js +120 -0
  177. package/management-ui/src/test/api/project.test.js +273 -0
  178. package/management-ui/src/test/components/Welcome.test.jsx +378 -0
  179. package/management-ui/src/test/mocks/server.js +178 -0
  180. package/management-ui/src/test/setup.js +61 -0
  181. package/management-ui/src/test/utils/test-utils.jsx +134 -0
  182. package/management-ui/src/utils/repository.js +98 -0
  183. package/management-ui/src/utils/repository.test.js +118 -0
  184. package/management-ui/src/workflows/phase2-integration-workflows.js +884 -0
  185. package/management-ui/tailwind.config.js +63 -0
  186. package/management-ui/tsconfig.json +37 -0
  187. package/management-ui/tsconfig.node.json +10 -0
  188. package/management-ui/vite.config.js +26 -0
  189. package/management-ui/vitest.config.js +38 -0
  190. package/package.json +16 -9
  191. package/infrastructure/app-handler-helpers.js +0 -57
  192. package/infrastructure/backend-utils.js +0 -90
  193. package/infrastructure/routers/auth.js +0 -26
  194. package/infrastructure/routers/integration-defined-routers.js +0 -37
  195. package/infrastructure/routers/middleware/loadUser.js +0 -15
  196. package/infrastructure/routers/middleware/requireLoggedInUser.js +0 -12
  197. package/infrastructure/routers/user.js +0 -41
  198. package/infrastructure/routers/websocket.js +0 -55
  199. package/infrastructure/workers/integration-defined-workers.js +0 -24
@@ -1,23 +1,518 @@
1
1
  const path = require('path');
2
2
  const fs = require('fs');
3
+ const { AWSDiscovery } = require('./aws-discovery');
4
+
5
+ /**
6
+ * Check if AWS discovery should run based on AppDefinition
7
+ * @param {Object} AppDefinition - Application definition
8
+ * @returns {boolean} True if discovery should run
9
+ */
10
+ const shouldRunDiscovery = (AppDefinition) => {
11
+ return AppDefinition.vpc?.enable === true ||
12
+ AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true ||
13
+ AppDefinition.ssm?.enable === true;
14
+ };
15
+
16
+ /**
17
+ * Find the actual path to node_modules directory
18
+ * Tries multiple methods to locate node_modules:
19
+ * 1. Traversing up from current directory
20
+ * 2. Using npm root command
21
+ * 3. Looking for package.json and adjacent node_modules
22
+ * @returns {string} Path to node_modules directory
23
+ */
24
+ const findNodeModulesPath = () => {
25
+ try {
26
+ // Method 1: Try to find node_modules by traversing up from current directory
27
+ let currentDir = process.cwd();
28
+ let nodeModulesPath = null;
29
+
30
+ // Traverse up to 5 levels to find node_modules
31
+ for (let i = 0; i < 5; i++) {
32
+ const potentialPath = path.join(currentDir, 'node_modules');
33
+ if (fs.existsSync(potentialPath)) {
34
+ nodeModulesPath = potentialPath;
35
+ console.log(`Found node_modules at: ${nodeModulesPath} (method 1)`);
36
+ break;
37
+ }
38
+ // Move up one directory
39
+ const parentDir = path.dirname(currentDir);
40
+ if (parentDir === currentDir) {
41
+ // We've reached the root
42
+ break;
43
+ }
44
+ currentDir = parentDir;
45
+ }
46
+
47
+ // Method 2: If method 1 fails, try using npm root command
48
+ if (!nodeModulesPath) {
49
+ try {
50
+ // This requires child_process, so let's require it here
51
+ const { execSync } = require('node:child_process');
52
+ const npmRoot = execSync('npm root', { encoding: 'utf8' }).trim();
53
+ if (fs.existsSync(npmRoot)) {
54
+ nodeModulesPath = npmRoot;
55
+ console.log(`Found node_modules at: ${nodeModulesPath} (method 2)`);
56
+ }
57
+ } catch (npmError) {
58
+ console.error('Error executing npm root:', npmError);
59
+ }
60
+ }
61
+
62
+ // Method 3: If all else fails, check for a package.json and assume node_modules is adjacent
63
+ if (!nodeModulesPath) {
64
+ currentDir = process.cwd();
65
+ for (let i = 0; i < 5; i++) {
66
+ const packageJsonPath = path.join(currentDir, 'package.json');
67
+ if (fs.existsSync(packageJsonPath)) {
68
+ const potentialNodeModules = path.join(currentDir, 'node_modules');
69
+ if (fs.existsSync(potentialNodeModules)) {
70
+ nodeModulesPath = potentialNodeModules;
71
+ console.log(`Found node_modules at: ${nodeModulesPath} (method 3)`);
72
+ break;
73
+ }
74
+ }
75
+ // Move up one directory
76
+ const parentDir = path.dirname(currentDir);
77
+ if (parentDir === currentDir) {
78
+ // We've reached the root
79
+ break;
80
+ }
81
+ currentDir = parentDir;
82
+ }
83
+ }
84
+
85
+ if (nodeModulesPath) {
86
+ return nodeModulesPath;
87
+ }
88
+
89
+ console.warn('Could not find node_modules path, falling back to default');
90
+ return path.resolve(process.cwd(), '../node_modules');
91
+ } catch (error) {
92
+ console.error('Error finding node_modules path:', error);
93
+ return path.resolve(process.cwd(), '../node_modules');
94
+ }
95
+ };
96
+
97
+ /**
98
+ * Modify handler paths to point to the correct node_modules location
99
+ * Only modifies paths when running in offline mode
100
+ * @param {Object} functions - Serverless functions configuration object
101
+ * @returns {Object} Modified functions object with updated handler paths
102
+ */
103
+ const modifyHandlerPaths = (functions) => {
104
+ // Check if we're running in offline mode
105
+ const isOffline = process.argv.includes('offline');
106
+ console.log('isOffline', isOffline);
107
+
108
+ if (!isOffline) {
109
+ console.log('Not in offline mode, skipping handler path modification');
110
+ return functions;
111
+ }
112
+
113
+ const nodeModulesPath = findNodeModulesPath();
114
+ const modifiedFunctions = { ...functions };
115
+
116
+ for (const functionName of Object.keys(modifiedFunctions)) {
117
+ console.log('functionName', functionName);
118
+ const functionDef = modifiedFunctions[functionName];
119
+ if (functionDef?.handler?.includes('node_modules/')) {
120
+ // Replace node_modules/ with the actual path to node_modules/
121
+ const relativePath = path.relative(process.cwd(), nodeModulesPath);
122
+ functionDef.handler = functionDef.handler.replace('node_modules/', `${relativePath}/`);
123
+ console.log(`Updated handler for ${functionName}: ${functionDef.handler}`);
124
+ }
125
+ }
126
+
127
+ return modifiedFunctions;
128
+ };
129
+
130
+ /**
131
+ * Create VPC infrastructure resources for CloudFormation
132
+ * Creates VPC, subnets, NAT gateway, route tables, and security groups
133
+ * @param {Object} AppDefinition - Application definition object
134
+ * @param {Object} AppDefinition.vpc - VPC configuration
135
+ * @param {string} [AppDefinition.vpc.cidrBlock='10.0.0.0/16'] - CIDR block for VPC
136
+ * @returns {Object} CloudFormation resources for VPC infrastructure
137
+ */
138
+ const createVPCInfrastructure = (AppDefinition) => {
139
+ const vpcResources = {
140
+ // VPC
141
+ FriggVPC: {
142
+ Type: 'AWS::EC2::VPC',
143
+ Properties: {
144
+ CidrBlock: AppDefinition.vpc.cidrBlock || '10.0.0.0/16',
145
+ EnableDnsHostnames: true,
146
+ EnableDnsSupport: true,
147
+ Tags: [
148
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc' }
149
+ ]
150
+ }
151
+ },
152
+
153
+ // Internet Gateway
154
+ FriggInternetGateway: {
155
+ Type: 'AWS::EC2::InternetGateway',
156
+ Properties: {
157
+ Tags: [
158
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-igw' }
159
+ ]
160
+ }
161
+ },
162
+
163
+ // Attach Internet Gateway to VPC
164
+ FriggVPCGatewayAttachment: {
165
+ Type: 'AWS::EC2::VPCGatewayAttachment',
166
+ Properties: {
167
+ VpcId: { Ref: 'FriggVPC' },
168
+ InternetGatewayId: { Ref: 'FriggInternetGateway' }
169
+ }
170
+ },
171
+
172
+ // Public Subnet for NAT Gateway
173
+ FriggPublicSubnet: {
174
+ Type: 'AWS::EC2::Subnet',
175
+ Properties: {
176
+ VpcId: { Ref: 'FriggVPC' },
177
+ CidrBlock: '10.0.1.0/24',
178
+ AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
179
+ MapPublicIpOnLaunch: true,
180
+ Tags: [
181
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-subnet' }
182
+ ]
183
+ }
184
+ },
185
+
186
+ // Private Subnet 1 for Lambda
187
+ FriggPrivateSubnet1: {
188
+ Type: 'AWS::EC2::Subnet',
189
+ Properties: {
190
+ VpcId: { Ref: 'FriggVPC' },
191
+ CidrBlock: '10.0.2.0/24',
192
+ AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
193
+ Tags: [
194
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-subnet-1' }
195
+ ]
196
+ }
197
+ },
198
+
199
+ // Private Subnet 2 for Lambda (different AZ for redundancy)
200
+ FriggPrivateSubnet2: {
201
+ Type: 'AWS::EC2::Subnet',
202
+ Properties: {
203
+ VpcId: { Ref: 'FriggVPC' },
204
+ CidrBlock: '10.0.3.0/24',
205
+ AvailabilityZone: { 'Fn::Select': [1, { 'Fn::GetAZs': '' }] },
206
+ Tags: [
207
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-subnet-2' }
208
+ ]
209
+ }
210
+ },
211
+
212
+ // Elastic IP for NAT Gateway
213
+ FriggNATGatewayEIP: {
214
+ Type: 'AWS::EC2::EIP',
215
+ Properties: {
216
+ Domain: 'vpc',
217
+ Tags: [
218
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-eip' }
219
+ ]
220
+ },
221
+ DependsOn: 'FriggVPCGatewayAttachment'
222
+ },
223
+
224
+ // NAT Gateway for private subnet internet access
225
+ FriggNATGateway: {
226
+ Type: 'AWS::EC2::NatGateway',
227
+ Properties: {
228
+ AllocationId: { 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] },
229
+ SubnetId: { Ref: 'FriggPublicSubnet' },
230
+ Tags: [
231
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-gateway' }
232
+ ]
233
+ }
234
+ },
235
+
236
+ // Public Route Table
237
+ FriggPublicRouteTable: {
238
+ Type: 'AWS::EC2::RouteTable',
239
+ Properties: {
240
+ VpcId: { Ref: 'FriggVPC' },
241
+ Tags: [
242
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-rt' }
243
+ ]
244
+ }
245
+ },
246
+
247
+ // Public Route to Internet Gateway
248
+ FriggPublicRoute: {
249
+ Type: 'AWS::EC2::Route',
250
+ Properties: {
251
+ RouteTableId: { Ref: 'FriggPublicRouteTable' },
252
+ DestinationCidrBlock: '0.0.0.0/0',
253
+ GatewayId: { Ref: 'FriggInternetGateway' }
254
+ },
255
+ DependsOn: 'FriggVPCGatewayAttachment'
256
+ },
257
+
258
+ // Associate Public Subnet with Public Route Table
259
+ FriggPublicSubnetRouteTableAssociation: {
260
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
261
+ Properties: {
262
+ SubnetId: { Ref: 'FriggPublicSubnet' },
263
+ RouteTableId: { Ref: 'FriggPublicRouteTable' }
264
+ }
265
+ },
266
+
267
+ // Private Route Table
268
+ FriggPrivateRouteTable: {
269
+ Type: 'AWS::EC2::RouteTable',
270
+ Properties: {
271
+ VpcId: { Ref: 'FriggVPC' },
272
+ Tags: [
273
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-rt' }
274
+ ]
275
+ }
276
+ },
277
+
278
+ // Private Route to NAT Gateway
279
+ FriggPrivateRoute: {
280
+ Type: 'AWS::EC2::Route',
281
+ Properties: {
282
+ RouteTableId: { Ref: 'FriggPrivateRouteTable' },
283
+ DestinationCidrBlock: '0.0.0.0/0',
284
+ NatGatewayId: { Ref: 'FriggNATGateway' }
285
+ }
286
+ },
287
+
288
+ // Associate Private Subnet 1 with Private Route Table
289
+ FriggPrivateSubnet1RouteTableAssociation: {
290
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
291
+ Properties: {
292
+ SubnetId: { Ref: 'FriggPrivateSubnet1' },
293
+ RouteTableId: { Ref: 'FriggPrivateRouteTable' }
294
+ }
295
+ },
296
+
297
+ // Associate Private Subnet 2 with Private Route Table
298
+ FriggPrivateSubnet2RouteTableAssociation: {
299
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
300
+ Properties: {
301
+ SubnetId: { Ref: 'FriggPrivateSubnet2' },
302
+ RouteTableId: { Ref: 'FriggPrivateRouteTable' }
303
+ }
304
+ },
305
+
306
+ // Security Group for Lambda functions
307
+ FriggLambdaSecurityGroup: {
308
+ Type: 'AWS::EC2::SecurityGroup',
309
+ Properties: {
310
+ GroupDescription: 'Security group for Frigg Lambda functions',
311
+ VpcId: { Ref: 'FriggVPC' },
312
+ SecurityGroupEgress: [
313
+ {
314
+ IpProtocol: 'tcp',
315
+ FromPort: 443,
316
+ ToPort: 443,
317
+ CidrIp: '0.0.0.0/0',
318
+ Description: 'HTTPS outbound'
319
+ },
320
+ {
321
+ IpProtocol: 'tcp',
322
+ FromPort: 80,
323
+ ToPort: 80,
324
+ CidrIp: '0.0.0.0/0',
325
+ Description: 'HTTP outbound'
326
+ },
327
+ {
328
+ IpProtocol: 'tcp',
329
+ FromPort: 53,
330
+ ToPort: 53,
331
+ CidrIp: '0.0.0.0/0',
332
+ Description: 'DNS TCP'
333
+ },
334
+ {
335
+ IpProtocol: 'udp',
336
+ FromPort: 53,
337
+ ToPort: 53,
338
+ CidrIp: '0.0.0.0/0',
339
+ Description: 'DNS UDP'
340
+ }
341
+ ],
342
+ Tags: [
343
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-sg' }
344
+ ]
345
+ }
346
+ }
347
+ };
348
+
349
+ // Add VPC Endpoints for cost optimization
350
+ if (AppDefinition.vpc.enableVPCEndpoints !== false) {
351
+ // S3 Gateway Endpoint (free)
352
+ vpcResources.FriggS3VPCEndpoint = {
353
+ Type: 'AWS::EC2::VPCEndpoint',
354
+ Properties: {
355
+ VpcId: { Ref: 'FriggVPC' },
356
+ ServiceName: 'com.amazonaws.${self:provider.region}.s3',
357
+ VpcEndpointType: 'Gateway',
358
+ RouteTableIds: [
359
+ { Ref: 'FriggPrivateRouteTable' }
360
+ ]
361
+ }
362
+ };
363
+
364
+ // DynamoDB Gateway Endpoint (free)
365
+ vpcResources.FriggDynamoDBVPCEndpoint = {
366
+ Type: 'AWS::EC2::VPCEndpoint',
367
+ Properties: {
368
+ VpcId: { Ref: 'FriggVPC' },
369
+ ServiceName: 'com.amazonaws.${self:provider.region}.dynamodb',
370
+ VpcEndpointType: 'Gateway',
371
+ RouteTableIds: [
372
+ { Ref: 'FriggPrivateRouteTable' }
373
+ ]
374
+ }
375
+ };
376
+
377
+ // KMS Interface Endpoint (paid, but useful if using KMS)
378
+ if (AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true) {
379
+ vpcResources.FriggKMSVPCEndpoint = {
380
+ Type: 'AWS::EC2::VPCEndpoint',
381
+ Properties: {
382
+ VpcId: { Ref: 'FriggVPC' },
383
+ ServiceName: 'com.amazonaws.${self:provider.region}.kms',
384
+ VpcEndpointType: 'Interface',
385
+ SubnetIds: [
386
+ { Ref: 'FriggPrivateSubnet1' },
387
+ { Ref: 'FriggPrivateSubnet2' }
388
+ ],
389
+ SecurityGroupIds: [
390
+ { Ref: 'FriggVPCEndpointSecurityGroup' }
391
+ ],
392
+ PrivateDnsEnabled: true
393
+ }
394
+ };
395
+ }
396
+
397
+ // Secrets Manager Interface Endpoint (paid, but useful for secrets)
398
+ vpcResources.FriggSecretsManagerVPCEndpoint = {
399
+ Type: 'AWS::EC2::VPCEndpoint',
400
+ Properties: {
401
+ VpcId: { Ref: 'FriggVPC' },
402
+ ServiceName: 'com.amazonaws.${self:provider.region}.secretsmanager',
403
+ VpcEndpointType: 'Interface',
404
+ SubnetIds: [
405
+ { Ref: 'FriggPrivateSubnet1' },
406
+ { Ref: 'FriggPrivateSubnet2' }
407
+ ],
408
+ SecurityGroupIds: [
409
+ { Ref: 'FriggVPCEndpointSecurityGroup' }
410
+ ],
411
+ PrivateDnsEnabled: true
412
+ }
413
+ };
414
+
415
+ // Security Group for VPC Endpoints
416
+ vpcResources.FriggVPCEndpointSecurityGroup = {
417
+ Type: 'AWS::EC2::SecurityGroup',
418
+ Properties: {
419
+ GroupDescription: 'Security group for Frigg VPC Endpoints',
420
+ VpcId: { Ref: 'FriggVPC' },
421
+ SecurityGroupIngress: [
422
+ {
423
+ IpProtocol: 'tcp',
424
+ FromPort: 443,
425
+ ToPort: 443,
426
+ SourceSecurityGroupId: { Ref: 'FriggLambdaSecurityGroup' },
427
+ Description: 'HTTPS from Lambda'
428
+ }
429
+ ],
430
+ Tags: [
431
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc-endpoint-sg' }
432
+ ]
433
+ }
434
+ };
435
+ }
436
+
437
+ return vpcResources;
438
+ };
439
+
440
+ /**
441
+ * Compose a complete serverless framework configuration from app definition
442
+ * @param {Object} AppDefinition - Application definition object
443
+ * @param {string} [AppDefinition.name] - Application name
444
+ * @param {string} [AppDefinition.provider='aws'] - Cloud provider
445
+ * @param {Array} AppDefinition.integrations - Array of integration definitions
446
+ * @param {Object} [AppDefinition.vpc] - VPC configuration
447
+ * @param {Object} [AppDefinition.encryption] - KMS encryption configuration
448
+ * @param {Object} [AppDefinition.ssm] - SSM parameter store configuration
449
+ * @param {Object} [AppDefinition.websockets] - WebSocket configuration
450
+ * @param {boolean} [AppDefinition.websockets.enable=false] - Enable WebSocket support for live update streaming
451
+ * @returns {Object} Complete serverless framework configuration
452
+ */
453
+ const composeServerlessDefinition = async (AppDefinition) => {
454
+ // Store discovered resources
455
+ let discoveredResources = {};
456
+
457
+ // Run AWS discovery if needed
458
+ if (shouldRunDiscovery(AppDefinition)) {
459
+ console.log('🔍 Running AWS resource discovery for serverless template...');
460
+ try {
461
+ const region = process.env.AWS_REGION || 'us-east-1';
462
+ const discovery = new AWSDiscovery(region);
463
+
464
+ const config = {
465
+ vpc: AppDefinition.vpc || {},
466
+ encryption: AppDefinition.encryption || {},
467
+ ssm: AppDefinition.ssm || {}
468
+ };
469
+
470
+ discoveredResources = await discovery.discoverResources(config);
471
+
472
+ console.log('✅ AWS discovery completed successfully!');
473
+ if (discoveredResources.defaultVpcId) {
474
+ console.log(` VPC: ${discoveredResources.defaultVpcId}`);
475
+ }
476
+ if (discoveredResources.privateSubnetId1 && discoveredResources.privateSubnetId2) {
477
+ console.log(` Subnets: ${discoveredResources.privateSubnetId1}, ${discoveredResources.privateSubnetId2}`);
478
+ }
479
+ if (discoveredResources.defaultSecurityGroupId) {
480
+ console.log(` Security Group: ${discoveredResources.defaultSecurityGroupId}`);
481
+ }
482
+ if (discoveredResources.defaultKmsKeyId) {
483
+ console.log(` KMS Key: ${discoveredResources.defaultKmsKeyId}`);
484
+ }
485
+ } catch (error) {
486
+ console.error('❌ AWS discovery failed:', error.message);
487
+ throw new Error(`AWS discovery failed: ${error.message}`);
488
+ }
489
+ }
3
490
 
4
- const composeServerlessDefinition = (AppDefinition, IntegrationFactory) => {
5
491
  const definition = {
6
492
  frameworkVersion: '>=3.17.0',
7
493
  service: AppDefinition.name || 'create-frigg-app',
8
494
  package: {
9
495
  individually: true,
496
+ exclude: ["!**/node_modules/aws-sdk/**", "!**/node_modules/@aws-sdk/**", "!package.json"],
10
497
  },
11
498
  useDotenv: true,
12
499
  provider: {
13
500
  name: AppDefinition.provider || 'aws',
14
501
  runtime: 'nodejs20.x',
15
502
  timeout: 30,
16
- region: 'us-east-1',
503
+ region: process.env.AWS_REGION || 'us-east-1',
17
504
  stage: '${opt:stage}',
18
505
  environment: {
19
- STAGE: '${opt:stage}',
506
+ STAGE: '${opt:stage, "dev"}',
20
507
  AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1,
508
+ // Add discovered resources to environment if available
509
+ ...(discoveredResources.defaultVpcId && { AWS_DISCOVERY_VPC_ID: discoveredResources.defaultVpcId }),
510
+ ...(discoveredResources.defaultSecurityGroupId && { AWS_DISCOVERY_SECURITY_GROUP_ID: discoveredResources.defaultSecurityGroupId }),
511
+ ...(discoveredResources.privateSubnetId1 && { AWS_DISCOVERY_SUBNET_ID_1: discoveredResources.privateSubnetId1 }),
512
+ ...(discoveredResources.privateSubnetId2 && { AWS_DISCOVERY_SUBNET_ID_2: discoveredResources.privateSubnetId2 }),
513
+ ...(discoveredResources.publicSubnetId && { AWS_DISCOVERY_PUBLIC_SUBNET_ID: discoveredResources.publicSubnetId }),
514
+ ...(discoveredResources.defaultRouteTableId && { AWS_DISCOVERY_ROUTE_TABLE_ID: discoveredResources.defaultRouteTableId }),
515
+ ...(discoveredResources.defaultKmsKeyId && { AWS_DISCOVERY_KMS_KEY_ID: discoveredResources.defaultKmsKeyId }),
21
516
  },
22
517
  iamRoleStatements: [
23
518
  {
@@ -27,9 +522,43 @@ const composeServerlessDefinition = (AppDefinition, IntegrationFactory) => {
27
522
  Ref: 'InternalErrorBridgeTopic',
28
523
  },
29
524
  },
525
+ {
526
+ Effect: 'Allow',
527
+ Action: [
528
+ 'sqs:SendMessage',
529
+ 'sqs:SendMessageBatch',
530
+ 'sqs:GetQueueUrl',
531
+ 'sqs:GetQueueAttributes'
532
+ ],
533
+ Resource: [
534
+ {
535
+ 'Fn::GetAtt': ['InternalErrorQueue', 'Arn']
536
+ },
537
+ {
538
+ 'Fn::Join': [
539
+ ':',
540
+ [
541
+ 'arn:aws:sqs:${self:provider.region}:*:${self:service}--${self:provider.stage}-*Queue'
542
+ ]
543
+ ]
544
+ }
545
+ ],
546
+ }
30
547
  ],
548
+ httpApi: {
549
+ payload: '2.0',
550
+ cors: {
551
+ allowedOrigins: ['*'],
552
+ allowedHeaders: ['*'],
553
+ allowedMethods: ['*'],
554
+ allowCredentials: false,
555
+ },
556
+ name: '${opt:stage, "dev"}-${self:service}',
557
+ disableDefaultEndpoint: false,
558
+ }
31
559
  },
32
560
  plugins: [
561
+ 'serverless-jetpack',
33
562
  'serverless-dotenv-plugin',
34
563
  'serverless-offline-sqs',
35
564
  'serverless-offline',
@@ -45,78 +574,63 @@ const composeServerlessDefinition = (AppDefinition, IntegrationFactory) => {
45
574
  autoCreate: false,
46
575
  apiVersion: '2012-11-05',
47
576
  endpoint: 'http://localhost:4566',
48
- region: 'us-east-1',
577
+ region: process.env.AWS_REGION || 'us-east-1',
49
578
  accessKeyId: 'root',
50
579
  secretAccessKey: 'root',
51
580
  skipCacheInvalidation: false,
52
581
  },
53
- webpack: {
54
- webpackConfig: 'webpack.config.js',
55
- includeModules: {
56
- forceExclude: ['aws-sdk'],
57
- },
58
- packager: 'npm',
59
- excludeFiles: ['src/**/*.test.js', 'test/'],
582
+ jetpack: {
583
+ base: '..',
60
584
  },
61
585
  },
62
586
  functions: {
63
- defaultWebsocket: {
64
- handler:
65
- '/../node_modules/@friggframework/devtools/infrastructure/routers/websocket.handler',
66
- events: [
67
- {
68
- websocket: {
69
- route: '$connect',
70
- },
71
- },
72
- {
73
- websocket: {
74
- route: '$default',
75
- },
76
- },
77
- {
78
- websocket: {
79
- route: '$disconnect',
80
- },
81
- },
82
- ],
83
- },
84
587
  auth: {
85
- handler:
86
- '/../node_modules/@friggframework/devtools/infrastructure/routers/auth.handler',
588
+ handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
87
589
  events: [
88
590
  {
89
- http: {
591
+ httpApi: {
90
592
  path: '/api/integrations',
91
593
  method: 'ANY',
92
- cors: true,
93
594
  },
94
595
  },
95
596
  {
96
- http: {
597
+ httpApi: {
97
598
  path: '/api/integrations/{proxy+}',
98
599
  method: 'ANY',
99
- cors: true,
100
600
  },
101
601
  },
102
602
  {
103
- http: {
603
+ httpApi: {
104
604
  path: '/api/authorize',
105
605
  method: 'ANY',
106
- cors: true,
107
606
  },
108
607
  },
109
608
  ],
110
609
  },
111
610
  user: {
112
- handler:
113
- '/../node_modules/@friggframework/devtools/infrastructure/routers/user.handler',
611
+ handler: 'node_modules/@friggframework/core/handlers/routers/user.handler',
114
612
  events: [
115
613
  {
116
- http: {
614
+ httpApi: {
117
615
  path: '/user/{proxy+}',
118
616
  method: 'ANY',
119
- cors: true,
617
+ },
618
+ },
619
+ ],
620
+ },
621
+ health: {
622
+ handler: 'node_modules/@friggframework/core/handlers/routers/health.handler',
623
+ events: [
624
+ {
625
+ httpApi: {
626
+ path: '/health',
627
+ method: 'GET',
628
+ },
629
+ },
630
+ {
631
+ httpApi: {
632
+ path: '/health/{proxy+}',
633
+ method: 'GET',
120
634
  },
121
635
  },
122
636
  ],
@@ -128,7 +642,7 @@ const composeServerlessDefinition = (AppDefinition, IntegrationFactory) => {
128
642
  Type: 'AWS::SQS::Queue',
129
643
  Properties: {
130
644
  QueueName:
131
- 'internal-error-queue-${self:provider.stage}',
645
+ '${self:service}-internal-error-queue-${self:provider.stage}',
132
646
  MessageRetentionPeriod: 300,
133
647
  },
134
648
  },
@@ -194,16 +708,12 @@ const composeServerlessDefinition = (AppDefinition, IntegrationFactory) => {
194
708
  AlarmActions: [{ Ref: 'InternalErrorBridgeTopic' }],
195
709
  Dimensions: [
196
710
  {
197
- Name: 'ApiName',
198
- Value: {
199
- 'Fn::Join': [
200
- '-',
201
- [
202
- '${self:provider.stage}',
203
- '${self:service}',
204
- ],
205
- ],
206
- },
711
+ Name: 'ApiId',
712
+ Value: { Ref: 'HttpApi' },
713
+ },
714
+ {
715
+ Name: 'Stage',
716
+ Value: '${self:provider.stage}',
207
717
  },
208
718
  ],
209
719
  },
@@ -212,80 +722,380 @@ const composeServerlessDefinition = (AppDefinition, IntegrationFactory) => {
212
722
  },
213
723
  };
214
724
 
725
+ // KMS Configuration based on App Definition
726
+ if (AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true) {
727
+ // Provision a dedicated KMS key and wire it automatically
728
+ definition.resources.Resources.FriggKMSKey = {
729
+ Type: 'AWS::KMS::Key',
730
+ Properties: {
731
+ EnableKeyRotation: true,
732
+ KeyPolicy: {
733
+ Version: '2012-10-17',
734
+ Statement: [
735
+ {
736
+ Sid: 'AllowRootAccountAdmin',
737
+ Effect: 'Allow',
738
+ Principal: { AWS: { 'Fn::Sub': 'arn:aws:iam::${AWS::AccountId}:root' } },
739
+ Action: 'kms:*',
740
+ Resource: '*'
741
+ }
742
+ ]
743
+ }
744
+ }
745
+ };
746
+
747
+ definition.provider.iamRoleStatements.push({
748
+ Effect: 'Allow',
749
+ Action: ['kms:GenerateDataKey', 'kms:Decrypt'],
750
+ Resource: [{ 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }]
751
+ });
752
+
753
+ definition.provider.environment.KMS_KEY_ARN = { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] };
754
+
755
+ definition.plugins.push('serverless-kms-grants');
756
+
757
+ // Configure KMS grants with discovered default key
758
+ definition.custom.kmsGrants = {
759
+ kmsKeyId: discoveredResources.defaultKmsKeyId || '${env:AWS_DISCOVERY_KMS_KEY_ID}'
760
+ };
761
+ }
762
+
763
+ // VPC Configuration based on App Definition
764
+ if (AppDefinition.vpc?.enable === true) {
765
+ // Add VPC-related IAM permissions
766
+ definition.provider.iamRoleStatements.push({
767
+ Effect: 'Allow',
768
+ Action: [
769
+ 'ec2:CreateNetworkInterface',
770
+ 'ec2:DescribeNetworkInterfaces',
771
+ 'ec2:DeleteNetworkInterface',
772
+ 'ec2:AttachNetworkInterface',
773
+ 'ec2:DetachNetworkInterface'
774
+ ],
775
+ Resource: '*'
776
+ });
777
+
778
+ // Default approach: Use AWS Discovery to find existing VPC resources
779
+ if (AppDefinition.vpc.createNew === true) {
780
+ // Option 1: Create new VPC infrastructure (explicit opt-in)
781
+ const vpcConfig = {};
782
+
783
+ if (AppDefinition.vpc.securityGroupIds) {
784
+ // User provided custom security groups
785
+ vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds;
786
+ } else {
787
+ // Use auto-created security group
788
+ vpcConfig.securityGroupIds = [{ Ref: 'FriggLambdaSecurityGroup' }];
789
+ }
790
+
791
+ if (AppDefinition.vpc.subnetIds) {
792
+ // User provided custom subnets
793
+ vpcConfig.subnetIds = AppDefinition.vpc.subnetIds;
794
+ } else {
795
+ // Use auto-created private subnets
796
+ vpcConfig.subnetIds = [
797
+ { Ref: 'FriggPrivateSubnet1' },
798
+ { Ref: 'FriggPrivateSubnet2' }
799
+ ];
800
+ }
801
+
802
+ // Set VPC config for Lambda functions
803
+ definition.provider.vpc = vpcConfig;
804
+
805
+ // Add VPC infrastructure resources to CloudFormation
806
+ const vpcResources = createVPCInfrastructure(AppDefinition);
807
+ Object.assign(definition.resources.Resources, vpcResources);
808
+ } else {
809
+ // Option 2: Use AWS Discovery (default behavior)
810
+ // VPC configuration using discovered or explicitly provided resources
811
+ const vpcConfig = {
812
+ securityGroupIds: AppDefinition.vpc.securityGroupIds ||
813
+ (discoveredResources.defaultSecurityGroupId ? [discoveredResources.defaultSecurityGroupId] : []),
814
+ subnetIds: AppDefinition.vpc.subnetIds ||
815
+ (discoveredResources.privateSubnetId1 && discoveredResources.privateSubnetId2 ?
816
+ [discoveredResources.privateSubnetId1, discoveredResources.privateSubnetId2] :
817
+ [])
818
+ };
819
+
820
+ // Set VPC config for Lambda functions only if we have valid subnet IDs
821
+ if (vpcConfig.subnetIds.length >= 2 && vpcConfig.securityGroupIds.length > 0) {
822
+ definition.provider.vpc = vpcConfig;
823
+
824
+ // Check if we have an existing NAT Gateway to use
825
+ if (!discoveredResources.existingNatGatewayId) {
826
+ // No existing NAT Gateway, create new resources
827
+
828
+ // Only create EIP if we don't have an existing one available
829
+ if (!discoveredResources.existingElasticIpAllocationId) {
830
+ definition.resources.Resources.FriggNATGatewayEIP = {
831
+ Type: 'AWS::EC2::EIP',
832
+ Properties: {
833
+ Domain: 'vpc',
834
+ Tags: [
835
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-eip' }
836
+ ]
837
+ }
838
+ };
839
+ }
840
+
841
+ definition.resources.Resources.FriggNATGateway = {
842
+ Type: 'AWS::EC2::NatGateway',
843
+ Properties: {
844
+ AllocationId: discoveredResources.existingElasticIpAllocationId ||
845
+ { 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] },
846
+ SubnetId: discoveredResources.publicSubnetId || discoveredResources.privateSubnetId1, // Use first discovered subnet if no public subnet found
847
+ Tags: [
848
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-gateway' }
849
+ ]
850
+ }
851
+ };
852
+ }
853
+
854
+ // Create route table for Lambda subnets to use NAT Gateway
855
+ definition.resources.Resources.FriggLambdaRouteTable = {
856
+ Type: 'AWS::EC2::RouteTable',
857
+ Properties: {
858
+ VpcId: discoveredResources.defaultVpcId || { Ref: 'FriggVPC' },
859
+ Tags: [
860
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-rt' }
861
+ ]
862
+ }
863
+ };
864
+
865
+ definition.resources.Resources.FriggNATRoute = {
866
+ Type: 'AWS::EC2::Route',
867
+ Properties: {
868
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' },
869
+ DestinationCidrBlock: '0.0.0.0/0',
870
+ NatGatewayId: discoveredResources.existingNatGatewayId || { Ref: 'FriggNATGateway' }
871
+ }
872
+ };
873
+
874
+ // Associate Lambda subnets with NAT Gateway route table
875
+ definition.resources.Resources.FriggSubnet1RouteAssociation = {
876
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
877
+ Properties: {
878
+ SubnetId: vpcConfig.subnetIds[0],
879
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' }
880
+ }
881
+ };
882
+
883
+ definition.resources.Resources.FriggSubnet2RouteAssociation = {
884
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
885
+ Properties: {
886
+ SubnetId: vpcConfig.subnetIds[1],
887
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' }
888
+ }
889
+ };
890
+
891
+ // Add VPC endpoints for AWS service optimization (optional but recommended)
892
+ if (AppDefinition.vpc.enableVPCEndpoints !== false) {
893
+ definition.resources.Resources.VPCEndpointS3 = {
894
+ Type: 'AWS::EC2::VPCEndpoint',
895
+ Properties: {
896
+ VpcId: discoveredResources.defaultVpcId,
897
+ ServiceName: 'com.amazonaws.${self:provider.region}.s3',
898
+ VpcEndpointType: 'Gateway',
899
+ RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }]
900
+ }
901
+ };
902
+
903
+ definition.resources.Resources.VPCEndpointDynamoDB = {
904
+ Type: 'AWS::EC2::VPCEndpoint',
905
+ Properties: {
906
+ VpcId: discoveredResources.defaultVpcId,
907
+ ServiceName: 'com.amazonaws.${self:provider.region}.dynamodb',
908
+ VpcEndpointType: 'Gateway',
909
+ RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }]
910
+ }
911
+ };
912
+ }
913
+ }
914
+ }
915
+
916
+ // SSM Parameter Store Configuration based on App Definition
917
+ if (AppDefinition.ssm?.enable === true) {
918
+ // Add AWS Parameters and Secrets Lambda Extension layer
919
+ definition.provider.layers = [
920
+ 'arn:aws:lambda:${self:provider.region}:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11'
921
+ ];
922
+
923
+ // Add SSM IAM permissions
924
+ definition.provider.iamRoleStatements.push({
925
+ Effect: 'Allow',
926
+ Action: [
927
+ 'ssm:GetParameter',
928
+ 'ssm:GetParameters',
929
+ 'ssm:GetParametersByPath'
930
+ ],
931
+ Resource: [
932
+ 'arn:aws:ssm:${self:provider.region}:*:parameter/${self:service}/${self:provider.stage}/*'
933
+ ]
934
+ });
935
+
936
+ // Add environment variable for SSM parameter prefix
937
+ definition.provider.environment.SSM_PARAMETER_PREFIX = '/${self:service}/${self:provider.stage}';
938
+ }
939
+
215
940
  // Add integration-specific functions and resources
216
- AppDefinition.integrations.forEach((integration) => {
217
- const integrationName = integration.Definition.name;
218
-
219
- // Add function for the integration
220
- definition.functions[integrationName] = {
221
- handler: `/../node_modules/@friggframework/devtools/infrastructure/routers/integration-defined-routers.handlers.${integrationName}.handler`,
222
- // events: integration.Definition.routes.map((route) => ({
223
- // http: {
224
- // path: `/api/${integrationName}-integration${route.path}`,
225
- // method: route.method || 'ANY',
226
- // cors: true,
227
- // },
228
- // })),
229
- events: [
230
- {
231
- http: {
232
- path: `/api/${integrationName}-integration/{proxy*}`,
233
- method: 'ANY',
234
- cors: true,
941
+ if (AppDefinition.integrations && Array.isArray(AppDefinition.integrations)) {
942
+ for (const integration of AppDefinition.integrations) {
943
+ if (!integration || !integration.Definition || !integration.Definition.name) {
944
+ throw new Error('Invalid integration: missing Definition or name');
945
+ }
946
+ const integrationName = integration.Definition.name;
947
+
948
+ // Add function for the integration
949
+ definition.functions[integrationName] = {
950
+ handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
951
+ events: [
952
+ {
953
+ httpApi: {
954
+ path: `/api/${integrationName}-integration/{proxy+}`,
955
+ method: 'ANY',
956
+ },
957
+ },
958
+ ],
959
+ };
960
+
961
+ // Add SQS Queue for the integration
962
+ const queueReference = `${integrationName.charAt(0).toUpperCase() + integrationName.slice(1)
963
+ }Queue`;
964
+ const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
965
+ definition.resources.Resources[queueReference] = {
966
+ Type: 'AWS::SQS::Queue',
967
+ Properties: {
968
+ QueueName: `\${self:custom.${queueReference}}`,
969
+ MessageRetentionPeriod: 60,
970
+ VisibilityTimeout: 1800, // 30 minutes
971
+ RedrivePolicy: {
972
+ maxReceiveCount: 1,
973
+ deadLetterTargetArn: {
974
+ 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
975
+ },
235
976
  },
236
977
  },
237
- ],
238
- };
978
+ };
239
979
 
240
- // Add SQS Queue for the integration
241
- const queueReference = `${
242
- integrationName.charAt(0).toUpperCase() + integrationName.slice(1)
243
- }Queue`;
244
- const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
245
- definition.resources.Resources[queueReference] = {
246
- Type: 'AWS::SQS::Queue',
247
- Properties: {
248
- QueueName: `\${self:custom.${queueReference}}`,
249
- MessageRetentionPeriod: 60,
250
- RedrivePolicy: {
251
- maxReceiveCount: 1,
252
- deadLetterTargetArn: {
253
- 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
980
+ // Add Queue Worker for the integration
981
+ const queueWorkerName = `${integrationName}QueueWorker`;
982
+ definition.functions[queueWorkerName] = {
983
+ handler: `node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.${integrationName}.queueWorker`,
984
+ reservedConcurrency: 5,
985
+ events: [
986
+ {
987
+ sqs: {
988
+ arn: {
989
+ 'Fn::GetAtt': [queueReference, 'Arn'],
990
+ },
991
+ batchSize: 1,
992
+ },
254
993
  },
994
+ ],
995
+ timeout: 600,
996
+ };
997
+
998
+ // Add Queue URL for the integration to the ENVironment variables
999
+ definition.provider.environment = {
1000
+ ...definition.provider.environment,
1001
+ [`${integrationName.toUpperCase()}_QUEUE_URL`]: {
1002
+ Ref: queueReference,
255
1003
  },
256
- },
257
- };
1004
+ };
1005
+
1006
+ definition.custom[queueReference] = queueName;
1007
+ }
1008
+ }
1009
+
1010
+ // Discovery has already run successfully at this point if needed
1011
+ // The discoveredResources object contains all the necessary AWS resources
1012
+
1013
+ // Add websocket function if enabled
1014
+ if (AppDefinition.websockets?.enable === true) {
1015
+ definition.functions.defaultWebsocket = {
1016
+ handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
1017
+ events: [
1018
+ {
1019
+ websocket: {
1020
+ route: '$connect',
1021
+ },
1022
+ },
1023
+ {
1024
+ websocket: {
1025
+ route: '$default',
1026
+ },
1027
+ },
1028
+ {
1029
+ websocket: {
1030
+ route: '$disconnect',
1031
+ },
1032
+ },
1033
+ ],
1034
+ };
1035
+ }
258
1036
 
259
- // Add Queue Worker for the integration
260
- const queueWorkerName = `${integrationName}QueueWorker`;
261
- definition.functions[queueWorkerName] = {
262
- handler: `/../node_modules/@friggframework/devtools/infrastructure/workers/integration-defined-workers.handlers.${integrationName}.queueWorker`,
263
- reservedConcurrency: 5,
1037
+ // Discovery has already run successfully at this point if needed
1038
+ // The discoveredResources object contains all the necessary AWS resources
1039
+
1040
+ // Add websocket function if enabled
1041
+ if (AppDefinition.websockets?.enable === true) {
1042
+ definition.functions.defaultWebsocket = {
1043
+ handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
1044
+ events: [
1045
+ {
1046
+ websocket: {
1047
+ route: '$connect',
1048
+ },
1049
+ },
1050
+ {
1051
+ websocket: {
1052
+ route: '$default',
1053
+ },
1054
+ },
1055
+ {
1056
+ websocket: {
1057
+ route: '$disconnect',
1058
+ },
1059
+ },
1060
+ ],
1061
+ };
1062
+ }
1063
+ }
1064
+
1065
+ // Discovery has already run successfully at this point if needed
1066
+ // The discoveredResources object contains all the necessary AWS resources
1067
+
1068
+ // Add websocket function if enabled
1069
+ if (AppDefinition.websockets?.enable === true) {
1070
+ definition.functions.defaultWebsocket = {
1071
+ handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
264
1072
  events: [
265
1073
  {
266
- sqs: {
267
- arn: {
268
- 'Fn::GetAtt': [queueReference, 'Arn'],
269
- },
270
- batchSize: 1,
1074
+ websocket: {
1075
+ route: '$connect',
1076
+ },
1077
+ },
1078
+ {
1079
+ websocket: {
1080
+ route: '$default',
1081
+ },
1082
+ },
1083
+ {
1084
+ websocket: {
1085
+ route: '$disconnect',
271
1086
  },
272
1087
  },
273
1088
  ],
274
- timeout: 600,
275
1089
  };
1090
+ }
276
1091
 
277
- // Add Queue URL for the integration to the ENVironment variables
278
- definition.provider.environment = {
279
- ...definition.provider.environment,
280
- [integrationName.toUpperCase() + '_QUEUE_URL']: {
281
- Ref: queueReference,
282
- },
283
- };
1092
+ // Discovery has already run successfully at this point if needed
1093
+ // The discoveredResources object contains all the necessary AWS resources
284
1094
 
285
- definition.custom[queueReference] = queueName;
286
- });
1095
+ // Modify handler paths to point to the correct node_modules location
1096
+ definition.functions = modifyHandlerPaths(definition.functions);
287
1097
 
288
1098
  return definition;
289
1099
  };
290
1100
 
291
- module.exports = { composeServerlessDefinition };
1101
+ module.exports = { composeServerlessDefinition };