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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/frigg-cli/.eslintrc.js +141 -0
  2. package/frigg-cli/__tests__/jest.config.js +102 -0
  3. package/frigg-cli/__tests__/unit/commands/build.test.js +483 -0
  4. package/frigg-cli/__tests__/unit/commands/install.test.js +418 -0
  5. package/frigg-cli/__tests__/unit/commands/ui.test.js +592 -0
  6. package/frigg-cli/__tests__/utils/command-tester.js +170 -0
  7. package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
  8. package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
  9. package/frigg-cli/__tests__/utils/test-setup.js +286 -0
  10. package/frigg-cli/build-command/index.js +54 -0
  11. package/frigg-cli/deploy-command/index.js +175 -0
  12. package/frigg-cli/generate-command/__tests__/generate-command.test.js +312 -0
  13. package/frigg-cli/generate-command/azure-generator.js +43 -0
  14. package/frigg-cli/generate-command/gcp-generator.js +47 -0
  15. package/frigg-cli/generate-command/index.js +332 -0
  16. package/frigg-cli/generate-command/terraform-generator.js +555 -0
  17. package/frigg-cli/generate-iam-command.js +115 -0
  18. package/frigg-cli/index.js +47 -1
  19. package/frigg-cli/index.test.js +1 -4
  20. package/frigg-cli/init-command/backend-first-handler.js +756 -0
  21. package/frigg-cli/init-command/index.js +93 -0
  22. package/frigg-cli/init-command/template-handler.js +143 -0
  23. package/frigg-cli/install-command/index.js +1 -4
  24. package/frigg-cli/package.json +51 -0
  25. package/frigg-cli/start-command/index.js +24 -4
  26. package/frigg-cli/test/init-command.test.js +180 -0
  27. package/frigg-cli/test/npm-registry.test.js +319 -0
  28. package/frigg-cli/ui-command/index.js +154 -0
  29. package/frigg-cli/utils/app-resolver.js +319 -0
  30. package/frigg-cli/utils/backend-path.js +16 -17
  31. package/frigg-cli/utils/npm-registry.js +167 -0
  32. package/frigg-cli/utils/process-manager.js +199 -0
  33. package/frigg-cli/utils/repo-detection.js +405 -0
  34. package/infrastructure/DEPLOYMENT-INSTRUCTIONS.md +268 -0
  35. package/infrastructure/GENERATE-IAM-DOCS.md +278 -0
  36. package/infrastructure/IAM-POLICY-TEMPLATES.md +176 -0
  37. package/infrastructure/README.md +443 -0
  38. package/infrastructure/WEBSOCKET-CONFIGURATION.md +105 -0
  39. package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
  40. package/infrastructure/__tests__/helpers/test-utils.js +277 -0
  41. package/infrastructure/aws-discovery.js +1176 -0
  42. package/infrastructure/aws-discovery.test.js +1220 -0
  43. package/infrastructure/build-time-discovery.js +206 -0
  44. package/infrastructure/build-time-discovery.test.js +378 -0
  45. package/infrastructure/create-frigg-infrastructure.js +3 -5
  46. package/infrastructure/env-validator.js +77 -0
  47. package/infrastructure/frigg-deployment-iam-stack.yaml +401 -0
  48. package/infrastructure/iam-generator.js +836 -0
  49. package/infrastructure/iam-generator.test.js +172 -0
  50. package/infrastructure/iam-policy-basic.json +218 -0
  51. package/infrastructure/iam-policy-full.json +288 -0
  52. package/infrastructure/integration.test.js +383 -0
  53. package/infrastructure/run-discovery.js +110 -0
  54. package/infrastructure/serverless-template.js +1472 -138
  55. package/infrastructure/serverless-template.test.js +1759 -0
  56. package/management-ui/.eslintrc.js +22 -0
  57. package/management-ui/README.md +203 -0
  58. package/management-ui/components.json +21 -0
  59. package/management-ui/docs/phase2-integration-guide.md +320 -0
  60. package/management-ui/index.html +13 -0
  61. package/management-ui/package-lock.json +16517 -0
  62. package/management-ui/package.json +76 -0
  63. package/management-ui/packages/devtools/frigg-cli/ui-command/index.js +302 -0
  64. package/management-ui/postcss.config.js +6 -0
  65. package/management-ui/server/api/backend.js +256 -0
  66. package/management-ui/server/api/cli.js +315 -0
  67. package/management-ui/server/api/codegen.js +663 -0
  68. package/management-ui/server/api/connections.js +857 -0
  69. package/management-ui/server/api/discovery.js +185 -0
  70. package/management-ui/server/api/environment/index.js +1 -0
  71. package/management-ui/server/api/environment/router.js +378 -0
  72. package/management-ui/server/api/environment.js +328 -0
  73. package/management-ui/server/api/integrations.js +876 -0
  74. package/management-ui/server/api/logs.js +248 -0
  75. package/management-ui/server/api/monitoring.js +282 -0
  76. package/management-ui/server/api/open-ide.js +31 -0
  77. package/management-ui/server/api/project.js +1029 -0
  78. package/management-ui/server/api/users/sessions.js +371 -0
  79. package/management-ui/server/api/users/simulation.js +254 -0
  80. package/management-ui/server/api/users.js +362 -0
  81. package/management-ui/server/api-contract.md +275 -0
  82. package/management-ui/server/index.js +873 -0
  83. package/management-ui/server/middleware/errorHandler.js +93 -0
  84. package/management-ui/server/middleware/security.js +32 -0
  85. package/management-ui/server/processManager.js +296 -0
  86. package/management-ui/server/server.js +346 -0
  87. package/management-ui/server/services/aws-monitor.js +413 -0
  88. package/management-ui/server/services/npm-registry.js +347 -0
  89. package/management-ui/server/services/template-engine.js +538 -0
  90. package/management-ui/server/utils/cliIntegration.js +220 -0
  91. package/management-ui/server/utils/environment/auditLogger.js +471 -0
  92. package/management-ui/server/utils/environment/awsParameterStore.js +264 -0
  93. package/management-ui/server/utils/environment/encryption.js +278 -0
  94. package/management-ui/server/utils/environment/envFileManager.js +286 -0
  95. package/management-ui/server/utils/import-commonjs.js +28 -0
  96. package/management-ui/server/utils/response.js +83 -0
  97. package/management-ui/server/websocket/handler.js +325 -0
  98. package/management-ui/src/App.jsx +109 -0
  99. package/management-ui/src/assets/FriggLogo.svg +1 -0
  100. package/management-ui/src/components/AppRouter.jsx +65 -0
  101. package/management-ui/src/components/Button.jsx +70 -0
  102. package/management-ui/src/components/Card.jsx +97 -0
  103. package/management-ui/src/components/EnvironmentCompare.jsx +400 -0
  104. package/management-ui/src/components/EnvironmentEditor.jsx +372 -0
  105. package/management-ui/src/components/EnvironmentImportExport.jsx +469 -0
  106. package/management-ui/src/components/EnvironmentSchema.jsx +491 -0
  107. package/management-ui/src/components/EnvironmentSecurity.jsx +463 -0
  108. package/management-ui/src/components/ErrorBoundary.jsx +73 -0
  109. package/management-ui/src/components/IntegrationCard.jsx +481 -0
  110. package/management-ui/src/components/IntegrationCardEnhanced.jsx +770 -0
  111. package/management-ui/src/components/IntegrationExplorer.jsx +379 -0
  112. package/management-ui/src/components/IntegrationStatus.jsx +336 -0
  113. package/management-ui/src/components/Layout.jsx +716 -0
  114. package/management-ui/src/components/LoadingSpinner.jsx +113 -0
  115. package/management-ui/src/components/RepositoryPicker.jsx +248 -0
  116. package/management-ui/src/components/SessionMonitor.jsx +350 -0
  117. package/management-ui/src/components/StatusBadge.jsx +208 -0
  118. package/management-ui/src/components/UserContextSwitcher.jsx +212 -0
  119. package/management-ui/src/components/UserSimulation.jsx +327 -0
  120. package/management-ui/src/components/Welcome.jsx +434 -0
  121. package/management-ui/src/components/codegen/APIEndpointGenerator.jsx +637 -0
  122. package/management-ui/src/components/codegen/APIModuleSelector.jsx +227 -0
  123. package/management-ui/src/components/codegen/CodeGenerationWizard.jsx +247 -0
  124. package/management-ui/src/components/codegen/CodePreviewEditor.jsx +316 -0
  125. package/management-ui/src/components/codegen/DynamicModuleForm.jsx +271 -0
  126. package/management-ui/src/components/codegen/FormBuilder.jsx +737 -0
  127. package/management-ui/src/components/codegen/IntegrationGenerator.jsx +855 -0
  128. package/management-ui/src/components/codegen/ProjectScaffoldWizard.jsx +797 -0
  129. package/management-ui/src/components/codegen/SchemaBuilder.jsx +303 -0
  130. package/management-ui/src/components/codegen/TemplateSelector.jsx +586 -0
  131. package/management-ui/src/components/codegen/index.js +10 -0
  132. package/management-ui/src/components/connections/ConnectionConfigForm.jsx +362 -0
  133. package/management-ui/src/components/connections/ConnectionHealthMonitor.jsx +182 -0
  134. package/management-ui/src/components/connections/ConnectionTester.jsx +200 -0
  135. package/management-ui/src/components/connections/EntityRelationshipMapper.jsx +292 -0
  136. package/management-ui/src/components/connections/OAuthFlow.jsx +204 -0
  137. package/management-ui/src/components/connections/index.js +5 -0
  138. package/management-ui/src/components/index.js +21 -0
  139. package/management-ui/src/components/monitoring/APIGatewayMetrics.jsx +222 -0
  140. package/management-ui/src/components/monitoring/LambdaMetrics.jsx +169 -0
  141. package/management-ui/src/components/monitoring/MetricsChart.jsx +197 -0
  142. package/management-ui/src/components/monitoring/MonitoringDashboard.jsx +393 -0
  143. package/management-ui/src/components/monitoring/SQSMetrics.jsx +246 -0
  144. package/management-ui/src/components/monitoring/index.js +6 -0
  145. package/management-ui/src/components/monitoring/monitoring.css +218 -0
  146. package/management-ui/src/components/theme-provider.jsx +52 -0
  147. package/management-ui/src/components/theme-toggle.jsx +39 -0
  148. package/management-ui/src/components/ui/badge.tsx +36 -0
  149. package/management-ui/src/components/ui/button.test.jsx +56 -0
  150. package/management-ui/src/components/ui/button.tsx +57 -0
  151. package/management-ui/src/components/ui/card.tsx +76 -0
  152. package/management-ui/src/components/ui/dropdown-menu.tsx +199 -0
  153. package/management-ui/src/components/ui/select.tsx +157 -0
  154. package/management-ui/src/components/ui/skeleton.jsx +15 -0
  155. package/management-ui/src/hooks/useFrigg.jsx +601 -0
  156. package/management-ui/src/hooks/useSocket.jsx +58 -0
  157. package/management-ui/src/index.css +193 -0
  158. package/management-ui/src/lib/utils.ts +6 -0
  159. package/management-ui/src/main.jsx +10 -0
  160. package/management-ui/src/pages/CodeGeneration.jsx +14 -0
  161. package/management-ui/src/pages/Connections.jsx +252 -0
  162. package/management-ui/src/pages/ConnectionsEnhanced.jsx +633 -0
  163. package/management-ui/src/pages/Dashboard.jsx +311 -0
  164. package/management-ui/src/pages/Environment.jsx +314 -0
  165. package/management-ui/src/pages/IntegrationConfigure.jsx +669 -0
  166. package/management-ui/src/pages/IntegrationDiscovery.jsx +567 -0
  167. package/management-ui/src/pages/IntegrationTest.jsx +742 -0
  168. package/management-ui/src/pages/Integrations.jsx +253 -0
  169. package/management-ui/src/pages/Monitoring.jsx +17 -0
  170. package/management-ui/src/pages/Simulation.jsx +155 -0
  171. package/management-ui/src/pages/Users.jsx +492 -0
  172. package/management-ui/src/services/api.js +41 -0
  173. package/management-ui/src/services/apiModuleService.js +193 -0
  174. package/management-ui/src/services/websocket-handlers.js +120 -0
  175. package/management-ui/src/test/api/project.test.js +273 -0
  176. package/management-ui/src/test/components/Welcome.test.jsx +378 -0
  177. package/management-ui/src/test/mocks/server.js +178 -0
  178. package/management-ui/src/test/setup.js +61 -0
  179. package/management-ui/src/test/utils/test-utils.jsx +134 -0
  180. package/management-ui/src/utils/repository.js +98 -0
  181. package/management-ui/src/utils/repository.test.js +118 -0
  182. package/management-ui/src/workflows/phase2-integration-workflows.js +884 -0
  183. package/management-ui/tailwind.config.js +63 -0
  184. package/management-ui/tsconfig.json +37 -0
  185. package/management-ui/tsconfig.node.json +10 -0
  186. package/management-ui/vite.config.js +26 -0
  187. package/management-ui/vitest.config.js +38 -0
  188. package/package.json +20 -9
  189. package/infrastructure/app-handler-helpers.js +0 -57
  190. package/infrastructure/backend-utils.js +0 -90
  191. package/infrastructure/routers/auth.js +0 -26
  192. package/infrastructure/routers/integration-defined-routers.js +0 -37
  193. package/infrastructure/routers/middleware/loadUser.js +0 -15
  194. package/infrastructure/routers/middleware/requireLoggedInUser.js +0 -12
  195. package/infrastructure/routers/user.js +0 -41
  196. package/infrastructure/routers/websocket.js +0 -55
  197. package/infrastructure/workers/integration-defined-workers.js +0 -24
@@ -0,0 +1,206 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ let AWSDiscovery;
4
+
5
+ function loadAWSDiscovery() {
6
+ if (!AWSDiscovery) {
7
+ ({ AWSDiscovery } = require('./aws-discovery'));
8
+ }
9
+ }
10
+
11
+ /**
12
+ * Build-time AWS resource discovery and configuration injection
13
+ * This utility runs during the build process to discover AWS resources
14
+ * and inject them into the serverless configuration
15
+ */
16
+ class BuildTimeDiscovery {
17
+ /**
18
+ * Creates an instance of BuildTimeDiscovery
19
+ * @param {string} [region=process.env.AWS_REGION || 'us-east-1'] - AWS region for discovery
20
+ */
21
+ constructor(region = process.env.AWS_REGION || 'us-east-1') {
22
+ loadAWSDiscovery();
23
+ this.region = region;
24
+ this.discovery = new AWSDiscovery(region);
25
+ }
26
+
27
+ /**
28
+ * Discover AWS resources and create a configuration file
29
+ * @param {string} [outputPath='./aws-discovery-config.json'] - Path to write the configuration file
30
+ * @returns {Promise<Object>} Configuration object containing discovered resources
31
+ * @throws {Error} If AWS resource discovery fails
32
+ */
33
+ async discoverAndCreateConfig(outputPath = './aws-discovery-config.json') {
34
+ try {
35
+ console.log('Starting AWS resource discovery for build...');
36
+
37
+ const resources = await this.discovery.discoverResources();
38
+
39
+ // Create configuration object
40
+ const config = {
41
+ awsDiscovery: resources,
42
+ generatedAt: new Date().toISOString(),
43
+ region: this.region
44
+ };
45
+
46
+ // Write configuration to file
47
+ fs.writeFileSync(outputPath, JSON.stringify(config, null, 2));
48
+ console.log(`AWS discovery configuration written to: ${outputPath}`);
49
+
50
+ return config;
51
+ } catch (error) {
52
+ console.error('Error during AWS resource discovery:', error.message);
53
+ throw error;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Replace placeholders in serverless template with discovered values
59
+ * @param {string} templateContent - The template content with placeholders
60
+ * @param {Object} discoveredResources - Object containing discovered AWS resource IDs
61
+ * @returns {string} Updated template content with placeholders replaced
62
+ */
63
+ replaceTemplateVariables(templateContent, discoveredResources) {
64
+ let updatedContent = templateContent;
65
+
66
+ // Replace AWS discovery placeholders
67
+ const replacements = {
68
+ '${self:custom.awsDiscovery.defaultVpcId}': discoveredResources.defaultVpcId,
69
+ '${self:custom.awsDiscovery.defaultSecurityGroupId}': discoveredResources.defaultSecurityGroupId,
70
+ '${self:custom.awsDiscovery.privateSubnetId1}': discoveredResources.privateSubnetId1,
71
+ '${self:custom.awsDiscovery.privateSubnetId2}': discoveredResources.privateSubnetId2,
72
+ '${self:custom.awsDiscovery.privateRouteTableId}': discoveredResources.privateRouteTableId,
73
+ '${self:custom.awsDiscovery.defaultKmsKeyId}': discoveredResources.defaultKmsKeyId
74
+ };
75
+
76
+ for (const [placeholder, value] of Object.entries(replacements)) {
77
+ // Use a more targeted replacement to avoid replacing similar strings
78
+ updatedContent = updatedContent.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), value);
79
+ }
80
+
81
+ return updatedContent;
82
+ }
83
+
84
+ /**
85
+ * Process serverless configuration and inject discovered resources
86
+ * @param {string} configPath - Path to the serverless configuration file
87
+ * @param {string} [outputPath=null] - Output path for updated config (defaults to overwriting original)
88
+ * @returns {Promise<Object>} Object containing discovered AWS resources
89
+ * @throws {Error} If processing the serverless configuration fails
90
+ */
91
+ async processServerlessConfig(configPath, outputPath = null) {
92
+ try {
93
+ console.log(`Processing serverless configuration: ${configPath}`);
94
+
95
+ // Read the current serverless configuration
96
+ const configContent = fs.readFileSync(configPath, 'utf8');
97
+
98
+ // Discover AWS resources
99
+ const resources = await this.discovery.discoverResources();
100
+
101
+ // Replace placeholders with discovered values
102
+ const updatedContent = this.replaceTemplateVariables(configContent, resources);
103
+
104
+ // Write to output file or overwrite original
105
+ const finalPath = outputPath || configPath;
106
+ fs.writeFileSync(finalPath, updatedContent);
107
+
108
+ console.log(`Updated serverless configuration written to: ${finalPath}`);
109
+
110
+ return resources;
111
+ } catch (error) {
112
+ console.error('Error processing serverless configuration:', error.message);
113
+ throw error;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Generate a custom serverless configuration section for discovered resources
119
+ * @param {Object} discoveredResources - Object containing discovered AWS resource IDs
120
+ * @returns {Object} Custom section object for serverless configuration
121
+ */
122
+ generateCustomSection(discoveredResources) {
123
+ return {
124
+ awsDiscovery: discoveredResources
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Pre-build hook to discover resources and prepare configuration
130
+ * @param {Object} appDefinition - Application definition object
131
+ * @param {string} region - AWS region for discovery
132
+ * @returns {Promise<Object|null>} Discovered resources or null if discovery not needed
133
+ * @throws {Error} If pre-build AWS discovery fails
134
+ */
135
+ async preBuildHook(appDefinition, region) {
136
+ try {
137
+ console.log('Running pre-build AWS discovery hook...');
138
+
139
+ // Only run discovery if VPC, KMS, or SSM features are enabled
140
+ const needsDiscovery = appDefinition.vpc?.enable ||
141
+ appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
142
+ appDefinition.ssm?.enable;
143
+
144
+ if (!needsDiscovery) {
145
+ console.log('No AWS discovery needed based on app definition');
146
+ return null;
147
+ }
148
+
149
+ // Create discovery instance with specified region
150
+ loadAWSDiscovery();
151
+ const discovery = new AWSDiscovery(region);
152
+ const resources = await discovery.discoverResources();
153
+
154
+ // Create environment variables for serverless
155
+ const envVars = {
156
+ AWS_DISCOVERY_VPC_ID: resources.defaultVpcId,
157
+ AWS_DISCOVERY_SECURITY_GROUP_ID: resources.defaultSecurityGroupId,
158
+ AWS_DISCOVERY_SUBNET_ID_1: resources.privateSubnetId1,
159
+ AWS_DISCOVERY_SUBNET_ID_2: resources.privateSubnetId2,
160
+ AWS_DISCOVERY_PUBLIC_SUBNET_ID: resources.publicSubnetId,
161
+ AWS_DISCOVERY_ROUTE_TABLE_ID: resources.privateRouteTableId,
162
+ AWS_DISCOVERY_KMS_KEY_ID: resources.defaultKmsKeyId // Keep consistent naming convention (even though it's an ARN)
163
+ };
164
+
165
+ // Set environment variables for serverless to use
166
+ Object.assign(process.env, envVars);
167
+
168
+ console.log('AWS discovery completed and environment variables set');
169
+ return resources;
170
+ } catch (error) {
171
+ console.error('Error in pre-build AWS discovery hook:', error.message);
172
+ throw error;
173
+ }
174
+ }
175
+ }
176
+
177
+ /**
178
+ * CLI utility function for build-time discovery
179
+ * @param {Object} [options={}] - Options for build-time discovery
180
+ * @param {string} [options.region=process.env.AWS_REGION || 'us-east-1'] - AWS region
181
+ * @param {string} [options.outputPath='./aws-discovery-config.json'] - Output path for config file
182
+ * @param {string} [options.configPath=null] - Path to existing serverless config to process
183
+ * @returns {Promise<Object>} Discovered AWS resources
184
+ */
185
+ async function runBuildTimeDiscovery(options = {}) {
186
+ const {
187
+ region = process.env.AWS_REGION || 'us-east-1',
188
+ outputPath = './aws-discovery-config.json',
189
+ configPath = null
190
+ } = options;
191
+
192
+ const discovery = new BuildTimeDiscovery(region);
193
+
194
+ if (configPath) {
195
+ // Process existing serverless configuration
196
+ return await discovery.processServerlessConfig(configPath);
197
+ } else {
198
+ // Just discover and create config file
199
+ return await discovery.discoverAndCreateConfig(outputPath);
200
+ }
201
+ }
202
+
203
+ module.exports = {
204
+ BuildTimeDiscovery,
205
+ runBuildTimeDiscovery
206
+ };
@@ -0,0 +1,378 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { BuildTimeDiscovery, runBuildTimeDiscovery } = require('./build-time-discovery');
4
+ const { AWSDiscovery } = require('./aws-discovery');
5
+
6
+ // Mock dependencies
7
+ jest.mock('fs');
8
+ jest.mock('./aws-discovery');
9
+
10
+ describe('BuildTimeDiscovery', () => {
11
+ let buildTimeDiscovery;
12
+ let mockAWSDiscovery;
13
+ const originalEnv = process.env;
14
+
15
+ beforeEach(() => {
16
+ buildTimeDiscovery = new BuildTimeDiscovery('us-east-1');
17
+
18
+ // Mock AWSDiscovery
19
+ mockAWSDiscovery = {
20
+ discoverResources: jest.fn(),
21
+ };
22
+ AWSDiscovery.mockImplementation(() => mockAWSDiscovery);
23
+
24
+ // Mock fs methods
25
+ fs.writeFileSync = jest.fn();
26
+ fs.readFileSync = jest.fn();
27
+
28
+ // Reset environment
29
+ process.env = { ...originalEnv };
30
+
31
+ jest.clearAllMocks();
32
+ });
33
+
34
+ afterEach(() => {
35
+ process.env = originalEnv;
36
+ });
37
+
38
+ describe('constructor', () => {
39
+ it('should initialize with default region', () => {
40
+ const discovery = new BuildTimeDiscovery();
41
+ expect(discovery.region).toBe('us-east-1');
42
+ });
43
+
44
+ it('should initialize with custom region', () => {
45
+ const discovery = new BuildTimeDiscovery('us-west-2');
46
+ expect(discovery.region).toBe('us-west-2');
47
+ });
48
+
49
+ it('should use AWS_REGION environment variable', () => {
50
+ process.env.AWS_REGION = 'eu-west-1';
51
+ const discovery = new BuildTimeDiscovery();
52
+ expect(discovery.region).toBe('eu-west-1');
53
+ });
54
+ });
55
+
56
+ describe('discoverAndCreateConfig', () => {
57
+ const mockResources = {
58
+ defaultVpcId: 'vpc-12345678',
59
+ vpcCidr: '172.31.0.0/16',
60
+ defaultSecurityGroupId: 'sg-12345678',
61
+ privateSubnetId1: 'subnet-1',
62
+ privateSubnetId2: 'subnet-2',
63
+ privateRouteTableId: 'rtb-12345678',
64
+ defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678'
65
+ };
66
+
67
+ it('should discover resources and create config file', async () => {
68
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
69
+
70
+ const result = await buildTimeDiscovery.discoverAndCreateConfig('./test-config.json');
71
+
72
+ expect(mockAWSDiscovery.discoverResources).toHaveBeenCalled();
73
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
74
+ './test-config.json',
75
+ expect.stringContaining('"awsDiscovery"')
76
+ );
77
+ expect(result.awsDiscovery).toEqual(mockResources);
78
+ expect(result.region).toBe('us-east-1');
79
+ expect(result.generatedAt).toBeDefined();
80
+ });
81
+
82
+ it('should use default output path', async () => {
83
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
84
+
85
+ await buildTimeDiscovery.discoverAndCreateConfig();
86
+
87
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
88
+ './aws-discovery-config.json',
89
+ expect.any(String)
90
+ );
91
+ });
92
+
93
+ it('should throw error when discovery fails', async () => {
94
+ const error = new Error('Discovery failed');
95
+ mockAWSDiscovery.discoverResources.mockRejectedValue(error);
96
+
97
+ await expect(buildTimeDiscovery.discoverAndCreateConfig()).rejects.toThrow('Discovery failed');
98
+ });
99
+ });
100
+
101
+ describe('replaceTemplateVariables', () => {
102
+ const mockResources = {
103
+ defaultVpcId: 'vpc-12345678',
104
+ vpcCidr: '172.31.0.0/16',
105
+ defaultSecurityGroupId: 'sg-12345678',
106
+ privateSubnetId1: 'subnet-1',
107
+ privateSubnetId2: 'subnet-2',
108
+ privateRouteTableId: 'rtb-12345678',
109
+ defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678'
110
+ };
111
+
112
+ it('should replace all AWS discovery placeholders', () => {
113
+ const templateContent = `
114
+ vpc:
115
+ id: \${self:custom.awsDiscovery.defaultVpcId}
116
+ securityGroups:
117
+ - \${self:custom.awsDiscovery.defaultSecurityGroupId}
118
+ subnets:
119
+ - \${self:custom.awsDiscovery.privateSubnetId1}
120
+ - \${self:custom.awsDiscovery.privateSubnetId2}
121
+ routeTable: \${self:custom.awsDiscovery.privateRouteTableId}
122
+ kms:
123
+ keyId: \${self:custom.awsDiscovery.defaultKmsKeyId}
124
+ `;
125
+
126
+ const result = buildTimeDiscovery.replaceTemplateVariables(templateContent, mockResources);
127
+
128
+ expect(result).toContain('vpc-12345678');
129
+ expect(result).toContain('sg-12345678');
130
+ expect(result).toContain('subnet-1');
131
+ expect(result).toContain('subnet-2');
132
+ expect(result).toContain('rtb-12345678');
133
+ expect(result).toContain('arn:aws:kms:us-east-1:123456789012:key/12345678');
134
+ expect(result).not.toContain('${self:custom.awsDiscovery');
135
+ });
136
+
137
+ it('should handle content without placeholders', () => {
138
+ const templateContent = `
139
+ service: my-service
140
+ provider:
141
+ name: aws
142
+ runtime: nodejs18.x
143
+ `;
144
+
145
+ const result = buildTimeDiscovery.replaceTemplateVariables(templateContent, mockResources);
146
+
147
+ expect(result).toBe(templateContent);
148
+ });
149
+
150
+ it('should handle multiple occurrences of same placeholder', () => {
151
+ const templateContent = `
152
+ vpc1: \${self:custom.awsDiscovery.defaultVpcId}
153
+ vpc2: \${self:custom.awsDiscovery.defaultVpcId}
154
+ `;
155
+
156
+ const result = buildTimeDiscovery.replaceTemplateVariables(templateContent, mockResources);
157
+
158
+ expect(result).toBe(`
159
+ vpc1: vpc-12345678
160
+ vpc2: vpc-12345678
161
+ `);
162
+ });
163
+ });
164
+
165
+ describe('processServerlessConfig', () => {
166
+ const mockConfigContent = `
167
+ provider:
168
+ vpc: \${self:custom.awsDiscovery.defaultVpcId}
169
+ kms: \${self:custom.awsDiscovery.defaultKmsKeyId}
170
+ `;
171
+
172
+ const mockResources = {
173
+ defaultVpcId: 'vpc-12345678',
174
+ defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678'
175
+ };
176
+
177
+ it('should process serverless config and update file', async () => {
178
+ fs.readFileSync.mockReturnValue(mockConfigContent);
179
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
180
+
181
+ const result = await buildTimeDiscovery.processServerlessConfig('./serverless.yml');
182
+
183
+ expect(fs.readFileSync).toHaveBeenCalledWith('./serverless.yml', 'utf8');
184
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
185
+ './serverless.yml',
186
+ expect.stringContaining('vpc-12345678')
187
+ );
188
+ expect(result).toEqual(mockResources);
189
+ });
190
+
191
+ it('should write to different output file when specified', async () => {
192
+ fs.readFileSync.mockReturnValue(mockConfigContent);
193
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
194
+
195
+ await buildTimeDiscovery.processServerlessConfig('./serverless.yml', './serverless-processed.yml');
196
+
197
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
198
+ './serverless-processed.yml',
199
+ expect.any(String)
200
+ );
201
+ });
202
+
203
+ it('should throw error when file read fails', async () => {
204
+ fs.readFileSync.mockImplementation(() => {
205
+ throw new Error('File not found');
206
+ });
207
+
208
+ await expect(buildTimeDiscovery.processServerlessConfig('./nonexistent.yml')).rejects.toThrow('File not found');
209
+ });
210
+ });
211
+
212
+ describe('generateCustomSection', () => {
213
+ it('should generate custom section with discovered resources', () => {
214
+ const mockResources = {
215
+ defaultVpcId: 'vpc-12345678',
216
+ defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678'
217
+ };
218
+
219
+ const result = buildTimeDiscovery.generateCustomSection(mockResources);
220
+
221
+ expect(result).toEqual({
222
+ awsDiscovery: mockResources
223
+ });
224
+ });
225
+ });
226
+
227
+ describe('preBuildHook', () => {
228
+ const mockResources = {
229
+ defaultVpcId: 'vpc-12345678',
230
+ vpcCidr: '172.31.0.0/16',
231
+ defaultSecurityGroupId: 'sg-12345678',
232
+ privateSubnetId1: 'subnet-1',
233
+ privateSubnetId2: 'subnet-2',
234
+ privateRouteTableId: 'rtb-12345678',
235
+ defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678'
236
+ };
237
+
238
+ it('should run discovery when VPC is enabled', async () => {
239
+ const appDefinition = {
240
+ vpc: { enable: true },
241
+ integrations: []
242
+ };
243
+
244
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
245
+
246
+ const result = await buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1');
247
+
248
+ expect(mockAWSDiscovery.discoverResources).toHaveBeenCalled();
249
+ expect(result).toEqual(mockResources);
250
+ expect(process.env.AWS_DISCOVERY_VPC_ID).toBe('vpc-12345678');
251
+ expect(process.env.AWS_DISCOVERY_KMS_KEY_ID).toBe('arn:aws:kms:us-east-1:123456789012:key/12345678');
252
+ });
253
+
254
+ it('should run discovery when KMS is enabled', async () => {
255
+ const appDefinition = {
256
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
257
+ integrations: []
258
+ };
259
+
260
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
261
+
262
+ const result = await buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1');
263
+
264
+ expect(mockAWSDiscovery.discoverResources).toHaveBeenCalled();
265
+ expect(result).toEqual(mockResources);
266
+ });
267
+
268
+ it('should run discovery when SSM is enabled', async () => {
269
+ const appDefinition = {
270
+ ssm: { enable: true },
271
+ integrations: []
272
+ };
273
+
274
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
275
+
276
+ const result = await buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1');
277
+
278
+ expect(mockAWSDiscovery.discoverResources).toHaveBeenCalled();
279
+ expect(result).toEqual(mockResources);
280
+ });
281
+
282
+ it('should skip discovery when no features are enabled', async () => {
283
+ const appDefinition = {
284
+ integrations: []
285
+ };
286
+
287
+ const result = await buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1');
288
+
289
+ expect(mockAWSDiscovery.discoverResources).not.toHaveBeenCalled();
290
+ expect(result).toBeNull();
291
+ });
292
+
293
+ it('should throw error when discovery fails', async () => {
294
+ const appDefinition = {
295
+ vpc: { enable: true },
296
+ integrations: []
297
+ };
298
+
299
+ const error = new Error('Discovery failed');
300
+ mockAWSDiscovery.discoverResources.mockRejectedValue(error);
301
+
302
+ await expect(buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1')).rejects.toThrow('Discovery failed');
303
+ });
304
+
305
+ it('should set all environment variables', async () => {
306
+ const appDefinition = {
307
+ vpc: { enable: true },
308
+ integrations: []
309
+ };
310
+
311
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
312
+
313
+ await buildTimeDiscovery.preBuildHook(appDefinition, 'us-east-1');
314
+
315
+ expect(process.env.AWS_DISCOVERY_VPC_ID).toBe('vpc-12345678');
316
+ expect(process.env.AWS_DISCOVERY_SECURITY_GROUP_ID).toBe('sg-12345678');
317
+ expect(process.env.AWS_DISCOVERY_SUBNET_ID_1).toBe('subnet-1');
318
+ expect(process.env.AWS_DISCOVERY_SUBNET_ID_2).toBe('subnet-2');
319
+ expect(process.env.AWS_DISCOVERY_ROUTE_TABLE_ID).toBe('rtb-12345678');
320
+ expect(process.env.AWS_DISCOVERY_KMS_KEY_ID).toBe('arn:aws:kms:us-east-1:123456789012:key/12345678');
321
+ });
322
+ });
323
+
324
+ describe('runBuildTimeDiscovery', () => {
325
+ it('should run discovery with default options', async () => {
326
+ const mockResources = { defaultVpcId: 'vpc-12345678' };
327
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
328
+
329
+ const result = await runBuildTimeDiscovery();
330
+
331
+ expect(result.awsDiscovery).toEqual(mockResources);
332
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
333
+ './aws-discovery-config.json',
334
+ expect.any(String)
335
+ );
336
+ });
337
+
338
+ it('should process config file when configPath provided', async () => {
339
+ const mockConfigContent = 'provider: aws';
340
+ const mockResources = { defaultVpcId: 'vpc-12345678' };
341
+
342
+ fs.readFileSync.mockReturnValue(mockConfigContent);
343
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
344
+
345
+ const result = await runBuildTimeDiscovery({
346
+ configPath: './serverless.yml'
347
+ });
348
+
349
+ expect(result).toEqual(mockResources);
350
+ expect(fs.readFileSync).toHaveBeenCalledWith('./serverless.yml', 'utf8');
351
+ });
352
+
353
+ it('should use custom region and output path', async () => {
354
+ const mockResources = { defaultVpcId: 'vpc-12345678' };
355
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
356
+
357
+ await runBuildTimeDiscovery({
358
+ region: 'eu-west-1',
359
+ outputPath: './custom-config.json'
360
+ });
361
+
362
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
363
+ './custom-config.json',
364
+ expect.any(String)
365
+ );
366
+ });
367
+
368
+ it('should use AWS_REGION environment variable when region not specified', async () => {
369
+ process.env.AWS_REGION = 'ap-southeast-1';
370
+ const mockResources = { defaultVpcId: 'vpc-12345678' };
371
+ mockAWSDiscovery.discoverResources.mockResolvedValue(mockResources);
372
+
373
+ const result = await runBuildTimeDiscovery();
374
+
375
+ expect(result.region).toBe('ap-southeast-1');
376
+ });
377
+ });
378
+ });
@@ -2,11 +2,9 @@ const path = require('path');
2
2
  const fs = require('fs-extra');
3
3
  const { composeServerlessDefinition } = require('./serverless-template');
4
4
 
5
- const {
6
- findNearestBackendPackageJson,
7
- } = require('../frigg-cli/utils/backend-path');
5
+ const { findNearestBackendPackageJson } = require('@friggframework/core');
8
6
 
9
- function createFriggInfrastructure() {
7
+ async function createFriggInfrastructure() {
10
8
  const backendPath = findNearestBackendPackageJson();
11
9
  if (!backendPath) {
12
10
  throw new Error('Could not find backend package.json');
@@ -25,7 +23,7 @@ function createFriggInfrastructure() {
25
23
  // __dirname,
26
24
  // './serverless-template.js'
27
25
  // ));
28
- const definition = composeServerlessDefinition(
26
+ const definition = await composeServerlessDefinition(
29
27
  appDefinition,
30
28
  backend.IntegrationFactory
31
29
  );
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Environment variable validator for Frigg applications
3
+ * Validates that required environment variables are present based on appDefinition
4
+ */
5
+
6
+ /**
7
+ * Validate environment variables against appDefinition
8
+ * @param {Object} AppDefinition - Application definition with environment config
9
+ * @returns {Object} Validation results with valid, missing, and warnings arrays
10
+ */
11
+ const validateEnvironmentVariables = (AppDefinition) => {
12
+ const results = {
13
+ valid: [],
14
+ missing: [],
15
+ warnings: [],
16
+ };
17
+
18
+ if (!AppDefinition.environment) {
19
+ return results;
20
+ }
21
+
22
+ console.log('🔍 Validating environment variables...');
23
+
24
+ for (const [key, value] of Object.entries(AppDefinition.environment)) {
25
+ if (value === true) {
26
+ if (process.env[key]) {
27
+ results.valid.push(key);
28
+ } else {
29
+ results.missing.push(key);
30
+ }
31
+ }
32
+ }
33
+
34
+ // Special handling for certain variables
35
+ if (results.missing.includes('NODE_ENV')) {
36
+ results.warnings.push('NODE_ENV not set, defaulting to "production"');
37
+ // Remove from missing since it has a default
38
+ results.missing = results.missing.filter((v) => v !== 'NODE_ENV');
39
+ }
40
+
41
+ // Report results
42
+ if (results.valid.length > 0) {
43
+ console.log(
44
+ ` ✅ Valid: ${results.valid.length} environment variables found`
45
+ );
46
+ }
47
+
48
+ if (results.missing.length > 0) {
49
+ console.log(` ⚠️ Missing: ${results.missing.join(', ')}`);
50
+ results.warnings.push(
51
+ `Missing ${results.missing.length} environment variables. These should be set in your CI/CD environment or .env file`
52
+ );
53
+ }
54
+
55
+ if (results.warnings.length > 0) {
56
+ results.warnings.forEach((warning) => {
57
+ console.log(` ⚠️ ${warning}`);
58
+ });
59
+ }
60
+
61
+ return results;
62
+ };
63
+
64
+ /**
65
+ * Check if all required environment variables are present
66
+ * @param {Object} AppDefinition - Application definition
67
+ * @returns {boolean} True if all required variables are present
68
+ */
69
+ const hasAllRequiredEnvVars = (AppDefinition) => {
70
+ const results = validateEnvironmentVariables(AppDefinition);
71
+ return results.missing.length === 0;
72
+ };
73
+
74
+ module.exports = {
75
+ validateEnvironmentVariables,
76
+ hasAllRequiredEnvVars,
77
+ };