@friggframework/devtools 2.0.0-next.3 → 2.0.0-next.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/frigg-cli/.eslintrc.js +141 -0
- package/frigg-cli/__tests__/jest.config.js +102 -0
- package/frigg-cli/__tests__/unit/commands/build.test.js +483 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +418 -0
- package/frigg-cli/__tests__/unit/commands/ui.test.js +592 -0
- package/frigg-cli/__tests__/utils/command-tester.js +170 -0
- package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
- package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
- package/frigg-cli/__tests__/utils/test-setup.js +286 -0
- package/frigg-cli/build-command/index.js +54 -0
- package/frigg-cli/deploy-command/index.js +36 -0
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +312 -0
- package/frigg-cli/generate-command/azure-generator.js +43 -0
- package/frigg-cli/generate-command/gcp-generator.js +47 -0
- package/frigg-cli/generate-command/index.js +332 -0
- package/frigg-cli/generate-command/terraform-generator.js +555 -0
- package/frigg-cli/generate-iam-command.js +115 -0
- package/frigg-cli/index.js +47 -1
- package/frigg-cli/index.test.js +1 -4
- package/frigg-cli/init-command/backend-first-handler.js +756 -0
- package/frigg-cli/init-command/index.js +93 -0
- package/frigg-cli/init-command/template-handler.js +143 -0
- package/frigg-cli/install-command/index.js +1 -4
- package/frigg-cli/package.json +51 -0
- package/frigg-cli/start-command/index.js +24 -4
- package/frigg-cli/test/init-command.test.js +180 -0
- package/frigg-cli/test/npm-registry.test.js +319 -0
- package/frigg-cli/ui-command/index.js +154 -0
- package/frigg-cli/utils/app-resolver.js +319 -0
- package/frigg-cli/utils/backend-path.js +16 -17
- package/frigg-cli/utils/npm-registry.js +167 -0
- package/frigg-cli/utils/process-manager.js +199 -0
- package/frigg-cli/utils/repo-detection.js +405 -0
- package/infrastructure/AWS-DISCOVERY-TROUBLESHOOTING.md +245 -0
- package/infrastructure/AWS-IAM-CREDENTIAL-NEEDS.md +596 -0
- package/infrastructure/DEPLOYMENT-INSTRUCTIONS.md +268 -0
- package/infrastructure/GENERATE-IAM-DOCS.md +253 -0
- package/infrastructure/IAM-POLICY-TEMPLATES.md +176 -0
- package/infrastructure/README-TESTING.md +332 -0
- package/infrastructure/README.md +421 -0
- package/infrastructure/WEBSOCKET-CONFIGURATION.md +105 -0
- package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
- package/infrastructure/__tests__/helpers/test-utils.js +277 -0
- package/infrastructure/aws-discovery.js +568 -0
- package/infrastructure/aws-discovery.test.js +373 -0
- package/infrastructure/build-time-discovery.js +206 -0
- package/infrastructure/build-time-discovery.test.js +375 -0
- package/infrastructure/create-frigg-infrastructure.js +3 -5
- package/infrastructure/frigg-deployment-iam-stack.yaml +379 -0
- package/infrastructure/iam-generator.js +687 -0
- package/infrastructure/iam-generator.test.js +169 -0
- package/infrastructure/iam-policy-basic.json +212 -0
- package/infrastructure/iam-policy-full.json +282 -0
- package/infrastructure/integration.test.js +383 -0
- package/infrastructure/run-discovery.js +110 -0
- package/infrastructure/serverless-template.js +923 -113
- package/infrastructure/serverless-template.test.js +541 -0
- package/management-ui/.eslintrc.js +22 -0
- package/management-ui/README.md +203 -0
- package/management-ui/components.json +21 -0
- package/management-ui/docs/phase2-integration-guide.md +320 -0
- package/management-ui/index.html +13 -0
- package/management-ui/package-lock.json +16517 -0
- package/management-ui/package.json +76 -0
- package/management-ui/packages/devtools/frigg-cli/ui-command/index.js +302 -0
- package/management-ui/postcss.config.js +6 -0
- package/management-ui/server/api/backend.js +256 -0
- package/management-ui/server/api/cli.js +315 -0
- package/management-ui/server/api/codegen.js +663 -0
- package/management-ui/server/api/connections.js +857 -0
- package/management-ui/server/api/discovery.js +185 -0
- package/management-ui/server/api/environment/index.js +1 -0
- package/management-ui/server/api/environment/router.js +378 -0
- package/management-ui/server/api/environment.js +328 -0
- package/management-ui/server/api/integrations.js +876 -0
- package/management-ui/server/api/logs.js +248 -0
- package/management-ui/server/api/monitoring.js +282 -0
- package/management-ui/server/api/open-ide.js +31 -0
- package/management-ui/server/api/project.js +1029 -0
- package/management-ui/server/api/users/sessions.js +371 -0
- package/management-ui/server/api/users/simulation.js +254 -0
- package/management-ui/server/api/users.js +362 -0
- package/management-ui/server/api-contract.md +275 -0
- package/management-ui/server/index.js +873 -0
- package/management-ui/server/middleware/errorHandler.js +93 -0
- package/management-ui/server/middleware/security.js +32 -0
- package/management-ui/server/processManager.js +296 -0
- package/management-ui/server/server.js +346 -0
- package/management-ui/server/services/aws-monitor.js +413 -0
- package/management-ui/server/services/npm-registry.js +347 -0
- package/management-ui/server/services/template-engine.js +538 -0
- package/management-ui/server/utils/cliIntegration.js +220 -0
- package/management-ui/server/utils/environment/auditLogger.js +471 -0
- package/management-ui/server/utils/environment/awsParameterStore.js +264 -0
- package/management-ui/server/utils/environment/encryption.js +278 -0
- package/management-ui/server/utils/environment/envFileManager.js +286 -0
- package/management-ui/server/utils/import-commonjs.js +28 -0
- package/management-ui/server/utils/response.js +83 -0
- package/management-ui/server/websocket/handler.js +325 -0
- package/management-ui/src/App.jsx +109 -0
- package/management-ui/src/assets/FriggLogo.svg +1 -0
- package/management-ui/src/components/AppRouter.jsx +65 -0
- package/management-ui/src/components/Button.jsx +70 -0
- package/management-ui/src/components/Card.jsx +97 -0
- package/management-ui/src/components/EnvironmentCompare.jsx +400 -0
- package/management-ui/src/components/EnvironmentEditor.jsx +372 -0
- package/management-ui/src/components/EnvironmentImportExport.jsx +469 -0
- package/management-ui/src/components/EnvironmentSchema.jsx +491 -0
- package/management-ui/src/components/EnvironmentSecurity.jsx +463 -0
- package/management-ui/src/components/ErrorBoundary.jsx +73 -0
- package/management-ui/src/components/IntegrationCard.jsx +481 -0
- package/management-ui/src/components/IntegrationCardEnhanced.jsx +770 -0
- package/management-ui/src/components/IntegrationExplorer.jsx +379 -0
- package/management-ui/src/components/IntegrationStatus.jsx +336 -0
- package/management-ui/src/components/Layout.jsx +716 -0
- package/management-ui/src/components/LoadingSpinner.jsx +113 -0
- package/management-ui/src/components/RepositoryPicker.jsx +248 -0
- package/management-ui/src/components/SessionMonitor.jsx +350 -0
- package/management-ui/src/components/StatusBadge.jsx +208 -0
- package/management-ui/src/components/UserContextSwitcher.jsx +212 -0
- package/management-ui/src/components/UserSimulation.jsx +327 -0
- package/management-ui/src/components/Welcome.jsx +434 -0
- package/management-ui/src/components/codegen/APIEndpointGenerator.jsx +637 -0
- package/management-ui/src/components/codegen/APIModuleSelector.jsx +227 -0
- package/management-ui/src/components/codegen/CodeGenerationWizard.jsx +247 -0
- package/management-ui/src/components/codegen/CodePreviewEditor.jsx +316 -0
- package/management-ui/src/components/codegen/DynamicModuleForm.jsx +271 -0
- package/management-ui/src/components/codegen/FormBuilder.jsx +737 -0
- package/management-ui/src/components/codegen/IntegrationGenerator.jsx +855 -0
- package/management-ui/src/components/codegen/ProjectScaffoldWizard.jsx +797 -0
- package/management-ui/src/components/codegen/SchemaBuilder.jsx +303 -0
- package/management-ui/src/components/codegen/TemplateSelector.jsx +586 -0
- package/management-ui/src/components/codegen/index.js +10 -0
- package/management-ui/src/components/connections/ConnectionConfigForm.jsx +362 -0
- package/management-ui/src/components/connections/ConnectionHealthMonitor.jsx +182 -0
- package/management-ui/src/components/connections/ConnectionTester.jsx +200 -0
- package/management-ui/src/components/connections/EntityRelationshipMapper.jsx +292 -0
- package/management-ui/src/components/connections/OAuthFlow.jsx +204 -0
- package/management-ui/src/components/connections/index.js +5 -0
- package/management-ui/src/components/index.js +21 -0
- package/management-ui/src/components/monitoring/APIGatewayMetrics.jsx +222 -0
- package/management-ui/src/components/monitoring/LambdaMetrics.jsx +169 -0
- package/management-ui/src/components/monitoring/MetricsChart.jsx +197 -0
- package/management-ui/src/components/monitoring/MonitoringDashboard.jsx +393 -0
- package/management-ui/src/components/monitoring/SQSMetrics.jsx +246 -0
- package/management-ui/src/components/monitoring/index.js +6 -0
- package/management-ui/src/components/monitoring/monitoring.css +218 -0
- package/management-ui/src/components/theme-provider.jsx +52 -0
- package/management-ui/src/components/theme-toggle.jsx +39 -0
- package/management-ui/src/components/ui/badge.tsx +36 -0
- package/management-ui/src/components/ui/button.test.jsx +56 -0
- package/management-ui/src/components/ui/button.tsx +57 -0
- package/management-ui/src/components/ui/card.tsx +76 -0
- package/management-ui/src/components/ui/dropdown-menu.tsx +199 -0
- package/management-ui/src/components/ui/select.tsx +157 -0
- package/management-ui/src/components/ui/skeleton.jsx +15 -0
- package/management-ui/src/hooks/useFrigg.jsx +601 -0
- package/management-ui/src/hooks/useSocket.jsx +58 -0
- package/management-ui/src/index.css +193 -0
- package/management-ui/src/lib/utils.ts +6 -0
- package/management-ui/src/main.jsx +10 -0
- package/management-ui/src/pages/CodeGeneration.jsx +14 -0
- package/management-ui/src/pages/Connections.jsx +252 -0
- package/management-ui/src/pages/ConnectionsEnhanced.jsx +633 -0
- package/management-ui/src/pages/Dashboard.jsx +311 -0
- package/management-ui/src/pages/Environment.jsx +314 -0
- package/management-ui/src/pages/IntegrationConfigure.jsx +669 -0
- package/management-ui/src/pages/IntegrationDiscovery.jsx +567 -0
- package/management-ui/src/pages/IntegrationTest.jsx +742 -0
- package/management-ui/src/pages/Integrations.jsx +253 -0
- package/management-ui/src/pages/Monitoring.jsx +17 -0
- package/management-ui/src/pages/Simulation.jsx +155 -0
- package/management-ui/src/pages/Users.jsx +492 -0
- package/management-ui/src/services/api.js +41 -0
- package/management-ui/src/services/apiModuleService.js +193 -0
- package/management-ui/src/services/websocket-handlers.js +120 -0
- package/management-ui/src/test/api/project.test.js +273 -0
- package/management-ui/src/test/components/Welcome.test.jsx +378 -0
- package/management-ui/src/test/mocks/server.js +178 -0
- package/management-ui/src/test/setup.js +61 -0
- package/management-ui/src/test/utils/test-utils.jsx +134 -0
- package/management-ui/src/utils/repository.js +98 -0
- package/management-ui/src/utils/repository.test.js +118 -0
- package/management-ui/src/workflows/phase2-integration-workflows.js +884 -0
- package/management-ui/tailwind.config.js +63 -0
- package/management-ui/tsconfig.json +37 -0
- package/management-ui/tsconfig.node.json +10 -0
- package/management-ui/vite.config.js +26 -0
- package/management-ui/vitest.config.js +38 -0
- package/package.json +17 -9
- package/infrastructure/app-handler-helpers.js +0 -57
- package/infrastructure/backend-utils.js +0 -90
- package/infrastructure/routers/auth.js +0 -26
- package/infrastructure/routers/integration-defined-routers.js +0 -37
- package/infrastructure/routers/middleware/loadUser.js +0 -15
- package/infrastructure/routers/middleware/requireLoggedInUser.js +0 -12
- package/infrastructure/routers/user.js +0 -41
- package/infrastructure/routers/websocket.js +0 -55
- 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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
591
|
+
httpApi: {
|
|
90
592
|
path: '/api/integrations',
|
|
91
593
|
method: 'ANY',
|
|
92
|
-
cors: true,
|
|
93
594
|
},
|
|
94
595
|
},
|
|
95
596
|
{
|
|
96
|
-
|
|
597
|
+
httpApi: {
|
|
97
598
|
path: '/api/integrations/{proxy+}',
|
|
98
599
|
method: 'ANY',
|
|
99
|
-
cors: true,
|
|
100
600
|
},
|
|
101
601
|
},
|
|
102
602
|
{
|
|
103
|
-
|
|
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
|
-
|
|
614
|
+
httpApi: {
|
|
117
615
|
path: '/user/{proxy+}',
|
|
118
616
|
method: 'ANY',
|
|
119
|
-
|
|
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: '
|
|
198
|
-
Value: {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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.
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
//
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
278
|
-
|
|
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
|
-
|
|
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 };
|