@friggframework/devtools 2.0.0--canary.406.78e2685.0 → 2.0.0--canary.398.dd443c7.0

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 (34) hide show
  1. package/frigg-cli/build-command/index.js +4 -2
  2. package/frigg-cli/deploy-command/index.js +5 -2
  3. package/frigg-cli/generate-iam-command.js +115 -0
  4. package/frigg-cli/index.js +11 -1
  5. package/infrastructure/AWS-DISCOVERY-TROUBLESHOOTING.md +245 -0
  6. package/infrastructure/AWS-IAM-CREDENTIAL-NEEDS.md +596 -0
  7. package/infrastructure/DEPLOYMENT-INSTRUCTIONS.md +268 -0
  8. package/infrastructure/GENERATE-IAM-DOCS.md +253 -0
  9. package/infrastructure/IAM-POLICY-TEMPLATES.md +176 -0
  10. package/infrastructure/README-TESTING.md +332 -0
  11. package/infrastructure/README.md +421 -0
  12. package/infrastructure/WEBSOCKET-CONFIGURATION.md +105 -0
  13. package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
  14. package/infrastructure/__tests__/helpers/test-utils.js +277 -0
  15. package/infrastructure/aws-discovery.js +568 -0
  16. package/infrastructure/aws-discovery.test.js +373 -0
  17. package/infrastructure/build-time-discovery.js +206 -0
  18. package/infrastructure/build-time-discovery.test.js +375 -0
  19. package/infrastructure/create-frigg-infrastructure.js +2 -2
  20. package/infrastructure/frigg-deployment-iam-stack.yaml +379 -0
  21. package/infrastructure/iam-generator.js +687 -0
  22. package/infrastructure/iam-generator.test.js +169 -0
  23. package/infrastructure/iam-policy-basic.json +212 -0
  24. package/infrastructure/iam-policy-full.json +282 -0
  25. package/infrastructure/integration.test.js +383 -0
  26. package/infrastructure/run-discovery.js +110 -0
  27. package/infrastructure/serverless-template.js +514 -167
  28. package/infrastructure/serverless-template.test.js +541 -0
  29. package/management-ui/dist/assets/FriggLogo-B7Xx8ZW1.svg +1 -0
  30. package/management-ui/dist/assets/index-BA21WgFa.js +1221 -0
  31. package/management-ui/dist/assets/index-CbM64Oba.js +1221 -0
  32. package/management-ui/dist/assets/index-CkvseXTC.css +1 -0
  33. package/management-ui/dist/index.html +14 -0
  34. package/package.json +9 -5
@@ -0,0 +1,373 @@
1
+ const AWS = require('aws-sdk');
2
+ const { AWSDiscovery } = require('./aws-discovery');
3
+
4
+ // Mock AWS SDK
5
+ jest.mock('@aws-sdk/client-ec2');
6
+ jest.mock('@aws-sdk/client-kms');
7
+ jest.mock('@aws-sdk/client-sts');
8
+
9
+ const { EC2Client, DescribeVpcsCommand, DescribeSubnetsCommand, DescribeSecurityGroupsCommand, DescribeRouteTablesCommand } = require('@aws-sdk/client-ec2');
10
+ const { KMSClient, ListKeysCommand, DescribeKeyCommand } = require('@aws-sdk/client-kms');
11
+ const { STSClient, GetCallerIdentityCommand } = require('@aws-sdk/client-sts');
12
+
13
+ describe('AWSDiscovery', () => {
14
+ let discovery;
15
+ let mockEC2Send;
16
+ let mockKMSSend;
17
+ let mockSTSSend;
18
+
19
+ beforeEach(() => {
20
+ discovery = new AWSDiscovery('us-east-1');
21
+
22
+ // Create mock send functions
23
+ mockEC2Send = jest.fn();
24
+ mockKMSSend = jest.fn();
25
+ mockSTSSend = jest.fn();
26
+
27
+ // Mock the client constructors and send methods
28
+ EC2Client.mockImplementation(() => ({
29
+ send: mockEC2Send
30
+ }));
31
+
32
+ KMSClient.mockImplementation(() => ({
33
+ send: mockKMSSend
34
+ }));
35
+
36
+ STSClient.mockImplementation(() => ({
37
+ send: mockSTSSend
38
+ }));
39
+
40
+ // Reset mocks
41
+ jest.clearAllMocks();
42
+ });
43
+
44
+ describe('getAccountId', () => {
45
+ it('should return AWS account ID', async () => {
46
+ const mockAccountId = '123456789012';
47
+ mockSTSSend.mockResolvedValue({
48
+ Account: mockAccountId
49
+ });
50
+
51
+ const result = await discovery.getAccountId();
52
+
53
+ expect(result).toBe(mockAccountId);
54
+ expect(mockSTSSend).toHaveBeenCalledWith(expect.any(GetCallerIdentityCommand));
55
+ });
56
+
57
+ it('should throw error when STS call fails', async () => {
58
+ const error = new Error('STS Error');
59
+ mockSTSSend.mockRejectedValue(error);
60
+
61
+ await expect(discovery.getAccountId()).rejects.toThrow('STS Error');
62
+ });
63
+ });
64
+
65
+ describe('findDefaultVpc', () => {
66
+ it('should return default VPC when found', async () => {
67
+ const mockVpc = {
68
+ VpcId: 'vpc-12345678',
69
+ IsDefault: true,
70
+ State: 'available'
71
+ };
72
+
73
+ mockEC2Send.mockResolvedValue({
74
+ Vpcs: [mockVpc]
75
+ });
76
+
77
+ const result = await discovery.findDefaultVpc();
78
+
79
+ expect(result).toEqual(mockVpc);
80
+ expect(mockEC2Send).toHaveBeenCalledWith(expect.objectContaining({
81
+ input: {
82
+ Filters: [{
83
+ Name: 'is-default',
84
+ Values: ['true']
85
+ }]
86
+ }
87
+ }));
88
+ });
89
+
90
+ it('should return first available VPC when no default VPC exists', async () => {
91
+ const mockVpc = {
92
+ VpcId: 'vpc-87654321',
93
+ IsDefault: false,
94
+ State: 'available'
95
+ };
96
+
97
+ mockEC2Send
98
+ .mockResolvedValueOnce({ Vpcs: [] }) // No default VPC
99
+ .mockResolvedValueOnce({ Vpcs: [mockVpc] }); // All VPCs
100
+
101
+ const result = await discovery.findDefaultVpc();
102
+
103
+ expect(result).toEqual(mockVpc);
104
+ expect(mockEC2Send).toHaveBeenCalledTimes(2);
105
+ });
106
+
107
+ it('should throw error when no VPCs found', async () => {
108
+ mockEC2Send
109
+ .mockResolvedValueOnce({ Vpcs: [] }) // No default VPC
110
+ .mockResolvedValueOnce({ Vpcs: [] }); // No VPCs at all
111
+
112
+ await expect(discovery.findDefaultVpc()).rejects.toThrow('No VPC found in the account');
113
+ });
114
+ });
115
+
116
+ describe('findPrivateSubnets', () => {
117
+ const mockVpcId = 'vpc-12345678';
118
+
119
+ it('should return private subnets when found', async () => {
120
+ const mockSubnets = [
121
+ { SubnetId: 'subnet-private-1', VpcId: mockVpcId },
122
+ { SubnetId: 'subnet-private-2', VpcId: mockVpcId }
123
+ ];
124
+
125
+ mockEC2Send
126
+ .mockResolvedValueOnce({ Subnets: mockSubnets }) // DescribeSubnets
127
+ .mockResolvedValue({ RouteTables: [] }); // DescribeRouteTables (private)
128
+
129
+ const result = await discovery.findPrivateSubnets(mockVpcId);
130
+
131
+ expect(result).toHaveLength(2);
132
+ expect(result[0].SubnetId).toBe('subnet-private-1');
133
+ expect(result[1].SubnetId).toBe('subnet-private-2');
134
+ });
135
+
136
+ it('should return at least 2 subnets even if mixed private/public', async () => {
137
+ const mockSubnets = [
138
+ { SubnetId: 'subnet-1', VpcId: mockVpcId },
139
+ { SubnetId: 'subnet-2', VpcId: mockVpcId },
140
+ { SubnetId: 'subnet-3', VpcId: mockVpcId }
141
+ ];
142
+
143
+ mockEC2Send
144
+ .mockResolvedValueOnce({ Subnets: mockSubnets }) // DescribeSubnets
145
+ .mockResolvedValue({ // Only one private subnet
146
+ RouteTables: [{
147
+ Routes: [] // No IGW route = private
148
+ }]
149
+ });
150
+
151
+ const result = await discovery.findPrivateSubnets(mockVpcId);
152
+
153
+ expect(result).toHaveLength(2);
154
+ });
155
+
156
+ it('should throw error when no subnets found', async () => {
157
+ mockEC2Send.mockResolvedValue({ Subnets: [] });
158
+
159
+ await expect(discovery.findPrivateSubnets(mockVpcId)).rejects.toThrow(`No subnets found in VPC ${mockVpcId}`);
160
+ });
161
+ });
162
+
163
+ describe('isSubnetPrivate', () => {
164
+ const mockSubnetId = 'subnet-12345678';
165
+
166
+ it('should return false for public subnet (has IGW route)', async () => {
167
+ mockEC2Send.mockResolvedValue({
168
+ RouteTables: [{
169
+ Routes: [{
170
+ GatewayId: 'igw-12345678',
171
+ DestinationCidrBlock: '0.0.0.0/0'
172
+ }]
173
+ }]
174
+ });
175
+
176
+ const result = await discovery.isSubnetPrivate(mockSubnetId);
177
+
178
+ expect(result).toBe(false);
179
+ });
180
+
181
+ it('should return true for private subnet (no IGW route)', async () => {
182
+ mockEC2Send.mockResolvedValue({
183
+ RouteTables: [{
184
+ Routes: [{
185
+ GatewayId: 'local',
186
+ DestinationCidrBlock: '10.0.0.0/16'
187
+ }]
188
+ }]
189
+ });
190
+
191
+ const result = await discovery.isSubnetPrivate(mockSubnetId);
192
+
193
+ expect(result).toBe(true);
194
+ });
195
+
196
+ it('should default to private on error', async () => {
197
+ mockEC2Send.mockRejectedValue(new Error('Route table error'));
198
+
199
+ const result = await discovery.isSubnetPrivate(mockSubnetId);
200
+
201
+ expect(result).toBe(true);
202
+ });
203
+ });
204
+
205
+ describe('findDefaultSecurityGroup', () => {
206
+ const mockVpcId = 'vpc-12345678';
207
+
208
+ it('should return Frigg security group when found', async () => {
209
+ const mockFriggSg = {
210
+ GroupId: 'sg-frigg-123',
211
+ GroupName: 'frigg-lambda-sg',
212
+ VpcId: mockVpcId
213
+ };
214
+
215
+ mockEC2Send.mockResolvedValue({
216
+ SecurityGroups: [mockFriggSg]
217
+ });
218
+
219
+ const result = await discovery.findDefaultSecurityGroup(mockVpcId);
220
+
221
+ expect(result).toEqual(mockFriggSg);
222
+ expect(mockEC2Send).toHaveBeenCalledWith(expect.objectContaining({
223
+ input: {
224
+ Filters: [
225
+ { Name: 'vpc-id', Values: [mockVpcId] },
226
+ { Name: 'group-name', Values: ['frigg-lambda-sg'] }
227
+ ]
228
+ }
229
+ }));
230
+ });
231
+
232
+ it('should fallback to default security group', async () => {
233
+ const mockDefaultSg = {
234
+ GroupId: 'sg-default-123',
235
+ GroupName: 'default',
236
+ VpcId: mockVpcId
237
+ };
238
+
239
+ mockEC2Send
240
+ .mockResolvedValueOnce({ SecurityGroups: [] }) // No Frigg SG
241
+ .mockResolvedValueOnce({ SecurityGroups: [mockDefaultSg] }); // Default SG
242
+
243
+ const result = await discovery.findDefaultSecurityGroup(mockVpcId);
244
+
245
+ expect(result).toEqual(mockDefaultSg);
246
+ expect(mockEC2Send).toHaveBeenCalledTimes(2);
247
+ });
248
+
249
+ it('should throw error when no security groups found', async () => {
250
+ mockEC2Send.mockResolvedValue({ SecurityGroups: [] });
251
+
252
+ await expect(discovery.findDefaultSecurityGroup(mockVpcId)).rejects.toThrow(`No security group found for VPC ${mockVpcId}`);
253
+ });
254
+ });
255
+
256
+ describe('findDefaultKmsKey', () => {
257
+ it('should return customer managed key when found', async () => {
258
+ const mockKeyId = 'key-12345678';
259
+ const mockKeyArn = 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012';
260
+
261
+ mockKMSSend
262
+ .mockResolvedValueOnce({ // ListKeys
263
+ Keys: [{ KeyId: mockKeyId }]
264
+ })
265
+ .mockResolvedValueOnce({ // DescribeKey
266
+ KeyMetadata: {
267
+ KeyId: mockKeyId,
268
+ Arn: mockKeyArn,
269
+ KeyManager: 'CUSTOMER',
270
+ KeyState: 'Enabled'
271
+ }
272
+ });
273
+
274
+ mockSTSSend.mockResolvedValue({ Account: '123456789012' });
275
+
276
+ const result = await discovery.findDefaultKmsKey();
277
+
278
+ expect(result).toBe(mockKeyArn);
279
+ });
280
+
281
+ it('should return wildcard pattern when no customer keys found', async () => {
282
+ mockKMSSend.mockResolvedValue({ Keys: [] });
283
+ mockSTSSend.mockResolvedValue({ Account: '123456789012' });
284
+
285
+ const result = await discovery.findDefaultKmsKey();
286
+
287
+ expect(result).toBe('arn:aws:kms:us-east-1:123456789012:key/*');
288
+ });
289
+
290
+ it('should return fallback on error', async () => {
291
+ mockKMSSend.mockRejectedValue(new Error('KMS Error'));
292
+
293
+ const result = await discovery.findDefaultKmsKey();
294
+
295
+ expect(result).toBe('*');
296
+ });
297
+ });
298
+
299
+ describe('discoverResources', () => {
300
+ it('should discover all AWS resources successfully', async () => {
301
+ const mockVpc = { VpcId: 'vpc-12345678' };
302
+ const mockSubnets = [
303
+ { SubnetId: 'subnet-1' },
304
+ { SubnetId: 'subnet-2' }
305
+ ];
306
+ const mockSecurityGroup = { GroupId: 'sg-12345678' };
307
+ const mockRouteTable = { RouteTableId: 'rtb-12345678' };
308
+ const mockKmsArn = 'arn:aws:kms:us-east-1:123456789012:key/12345678';
309
+
310
+ // Mock all the discovery methods
311
+ jest.spyOn(discovery, 'findDefaultVpc').mockResolvedValue(mockVpc);
312
+ jest.spyOn(discovery, 'findPrivateSubnets').mockResolvedValue(mockSubnets);
313
+ jest.spyOn(discovery, 'findDefaultSecurityGroup').mockResolvedValue(mockSecurityGroup);
314
+ jest.spyOn(discovery, 'findPrivateRouteTable').mockResolvedValue(mockRouteTable);
315
+ jest.spyOn(discovery, 'findDefaultKmsKey').mockResolvedValue(mockKmsArn);
316
+
317
+ const result = await discovery.discoverResources();
318
+
319
+ expect(result).toEqual({
320
+ defaultVpcId: 'vpc-12345678',
321
+ defaultSecurityGroupId: 'sg-12345678',
322
+ privateSubnetId1: 'subnet-1',
323
+ privateSubnetId2: 'subnet-2',
324
+ privateRouteTableId: 'rtb-12345678',
325
+ defaultKmsKeyId: mockKmsArn
326
+ });
327
+
328
+ // Verify all methods were called
329
+ expect(discovery.findDefaultVpc).toHaveBeenCalled();
330
+ expect(discovery.findPrivateSubnets).toHaveBeenCalledWith('vpc-12345678');
331
+ expect(discovery.findDefaultSecurityGroup).toHaveBeenCalledWith('vpc-12345678');
332
+ expect(discovery.findPrivateRouteTable).toHaveBeenCalledWith('vpc-12345678');
333
+ expect(discovery.findDefaultKmsKey).toHaveBeenCalled();
334
+ });
335
+
336
+ it('should handle single subnet scenario', async () => {
337
+ const mockVpc = { VpcId: 'vpc-12345678' };
338
+ const mockSubnets = [{ SubnetId: 'subnet-1' }]; // Only one subnet
339
+ const mockSecurityGroup = { GroupId: 'sg-12345678' };
340
+ const mockRouteTable = { RouteTableId: 'rtb-12345678' };
341
+ const mockKmsArn = 'arn:aws:kms:us-east-1:123456789012:key/12345678';
342
+
343
+ jest.spyOn(discovery, 'findDefaultVpc').mockResolvedValue(mockVpc);
344
+ jest.spyOn(discovery, 'findPrivateSubnets').mockResolvedValue(mockSubnets);
345
+ jest.spyOn(discovery, 'findDefaultSecurityGroup').mockResolvedValue(mockSecurityGroup);
346
+ jest.spyOn(discovery, 'findPrivateRouteTable').mockResolvedValue(mockRouteTable);
347
+ jest.spyOn(discovery, 'findDefaultKmsKey').mockResolvedValue(mockKmsArn);
348
+
349
+ const result = await discovery.discoverResources();
350
+
351
+ expect(result.privateSubnetId1).toBe('subnet-1');
352
+ expect(result.privateSubnetId2).toBe('subnet-1'); // Should duplicate single subnet
353
+ });
354
+
355
+ it('should throw error when discovery fails', async () => {
356
+ jest.spyOn(discovery, 'findDefaultVpc').mockRejectedValue(new Error('VPC discovery failed'));
357
+
358
+ await expect(discovery.discoverResources()).rejects.toThrow('VPC discovery failed');
359
+ });
360
+ });
361
+
362
+ describe('constructor', () => {
363
+ it('should initialize with default region', () => {
364
+ const defaultDiscovery = new AWSDiscovery();
365
+ expect(defaultDiscovery.region).toBe('us-east-1');
366
+ });
367
+
368
+ it('should initialize with custom region', () => {
369
+ const customDiscovery = new AWSDiscovery('us-west-2');
370
+ expect(customDiscovery.region).toBe('us-west-2');
371
+ });
372
+ });
373
+ });
@@ -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?.useDefaultKMSForFieldLevelEncryption ||
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
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
+ };