@friggframework/devtools 2.0.0--canary.461.ec909cf.0 → 2.0.0--canary.461.9483dbe.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 (71) hide show
  1. package/frigg-cli/__tests__/unit/commands/build.test.js +6 -6
  2. package/frigg-cli/build-command/index.js +1 -1
  3. package/frigg-cli/deploy-command/index.js +6 -6
  4. package/frigg-cli/generate-command/index.js +2 -2
  5. package/frigg-cli/generate-iam-command.js +10 -10
  6. package/frigg-cli/start-command/index.js +1 -1
  7. package/frigg-cli/start-command/start-command.test.js +3 -3
  8. package/frigg-cli/utils/database-validator.js +14 -21
  9. package/infrastructure/REFACTOR.md +532 -0
  10. package/infrastructure/TRANSFORMATION-VISUAL.md +239 -0
  11. package/infrastructure/__tests__/postgres-config.test.js +1 -1
  12. package/infrastructure/create-frigg-infrastructure.js +1 -1
  13. package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
  14. package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
  15. package/infrastructure/domains/database/aurora-discovery.js +81 -0
  16. package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
  17. package/infrastructure/domains/integration/integration-builder.js +178 -0
  18. package/infrastructure/domains/integration/integration-builder.test.js +362 -0
  19. package/infrastructure/domains/integration/websocket-builder.js +69 -0
  20. package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
  21. package/infrastructure/domains/networking/vpc-discovery.test.js +257 -0
  22. package/infrastructure/domains/parameters/ssm-builder.js +79 -0
  23. package/infrastructure/domains/parameters/ssm-builder.test.js +188 -0
  24. package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
  25. package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
  26. package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
  27. package/infrastructure/domains/security/kms-builder.js +169 -0
  28. package/infrastructure/domains/security/kms-builder.test.js +354 -0
  29. package/infrastructure/domains/security/kms-discovery.js +80 -0
  30. package/infrastructure/domains/security/kms-discovery.test.js +176 -0
  31. package/infrastructure/domains/shared/base-builder.js +112 -0
  32. package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
  33. package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
  34. package/infrastructure/domains/shared/environment-builder.js +118 -0
  35. package/infrastructure/domains/shared/environment-builder.test.js +246 -0
  36. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +366 -0
  37. package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
  38. package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
  39. package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
  40. package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
  41. package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
  42. package/infrastructure/domains/shared/resource-discovery.js +132 -0
  43. package/infrastructure/domains/shared/resource-discovery.test.js +410 -0
  44. package/infrastructure/domains/shared/utilities/base-definition-factory.js +2 -3
  45. package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
  46. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -0
  47. package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +259 -0
  48. package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
  49. package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +134 -0
  50. package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
  51. package/infrastructure/esbuild.config.js +53 -0
  52. package/infrastructure/infrastructure-composer.js +85 -0
  53. package/infrastructure/scripts/build-prisma-layer.js +60 -47
  54. package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
  55. package/layers/prisma/nodejs/package.json +8 -0
  56. package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
  57. package/package.json +8 -8
  58. package/infrastructure/aws-discovery.js +0 -1704
  59. package/infrastructure/aws-discovery.test.js +0 -1666
  60. package/infrastructure/serverless-template.js +0 -2804
  61. package/infrastructure/serverless-template.test.js +0 -1897
  62. /package/infrastructure/{POSTGRES-CONFIGURATION.md → docs/POSTGRES-CONFIGURATION.md} +0 -0
  63. /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
  64. /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
  65. /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
  66. /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
  67. /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
  68. /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
  69. /package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +0 -0
  70. /package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +0 -0
  71. /package/infrastructure/{run-discovery.js → scripts/run-discovery.js} +0 -0
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Tests for WebSocket Builder
3
+ *
4
+ * Tests WebSocket API Gateway configuration
5
+ */
6
+
7
+ const { WebsocketBuilder } = require('./websocket-builder');
8
+ const { ValidationResult } = require('../shared/base-builder');
9
+
10
+ describe('WebsocketBuilder', () => {
11
+ let websocketBuilder;
12
+
13
+ beforeEach(() => {
14
+ websocketBuilder = new WebsocketBuilder();
15
+ delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
16
+ });
17
+
18
+ afterEach(() => {
19
+ delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
20
+ });
21
+
22
+ describe('shouldExecute()', () => {
23
+ it('should return true when websockets are enabled', () => {
24
+ const appDefinition = {
25
+ websockets: { enable: true },
26
+ };
27
+
28
+ expect(websocketBuilder.shouldExecute(appDefinition)).toBe(true);
29
+ });
30
+
31
+ it('should return false when websockets are disabled', () => {
32
+ const appDefinition = {
33
+ websockets: { enable: false },
34
+ };
35
+
36
+ expect(websocketBuilder.shouldExecute(appDefinition)).toBe(false);
37
+ });
38
+
39
+ it('should return false when websockets are not defined', () => {
40
+ const appDefinition = {};
41
+
42
+ expect(websocketBuilder.shouldExecute(appDefinition)).toBe(false);
43
+ });
44
+
45
+ it('should return false when FRIGG_SKIP_AWS_DISCOVERY is set (local mode)', () => {
46
+ process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
47
+ const appDefinition = {
48
+ websockets: { enable: true },
49
+ };
50
+
51
+ expect(websocketBuilder.shouldExecute(appDefinition)).toBe(false);
52
+ });
53
+ });
54
+
55
+ describe('validate()', () => {
56
+ it('should pass validation for valid websocket config', () => {
57
+ const appDefinition = {
58
+ websockets: {
59
+ enable: true,
60
+ },
61
+ };
62
+
63
+ const result = websocketBuilder.validate(appDefinition);
64
+
65
+ expect(result).toBeInstanceOf(ValidationResult);
66
+ expect(result.valid).toBe(true);
67
+ expect(result.errors).toEqual([]);
68
+ });
69
+
70
+ it('should error when websocket configuration is missing', () => {
71
+ const appDefinition = {};
72
+
73
+ const result = websocketBuilder.validate(appDefinition);
74
+
75
+ expect(result.valid).toBe(false);
76
+ expect(result.errors).toContain('WebSocket configuration is missing');
77
+ });
78
+ });
79
+
80
+ describe('build()', () => {
81
+ it('should create defaultWebsocket function', async () => {
82
+ const appDefinition = {
83
+ websockets: {
84
+ enable: true,
85
+ },
86
+ };
87
+
88
+ const result = await websocketBuilder.build(appDefinition, {});
89
+
90
+ expect(result.functions.defaultWebsocket).toBeDefined();
91
+ });
92
+
93
+ it('should configure correct handler path', async () => {
94
+ const appDefinition = {
95
+ websockets: {
96
+ enable: true,
97
+ },
98
+ };
99
+
100
+ const result = await websocketBuilder.build(appDefinition, {});
101
+
102
+ expect(result.functions.defaultWebsocket.handler).toBe(
103
+ 'node_modules/@friggframework/core/handlers/routers/websocket.handler'
104
+ );
105
+ });
106
+
107
+ it('should configure $connect route', async () => {
108
+ const appDefinition = {
109
+ websockets: {
110
+ enable: true,
111
+ },
112
+ };
113
+
114
+ const result = await websocketBuilder.build(appDefinition, {});
115
+
116
+ const connectEvent = result.functions.defaultWebsocket.events.find(
117
+ e => e.websocket?.route === '$connect'
118
+ );
119
+
120
+ expect(connectEvent).toBeDefined();
121
+ });
122
+
123
+ it('should configure $default route', async () => {
124
+ const appDefinition = {
125
+ websockets: {
126
+ enable: true,
127
+ },
128
+ };
129
+
130
+ const result = await websocketBuilder.build(appDefinition, {});
131
+
132
+ const defaultEvent = result.functions.defaultWebsocket.events.find(
133
+ e => e.websocket?.route === '$default'
134
+ );
135
+
136
+ expect(defaultEvent).toBeDefined();
137
+ });
138
+
139
+ it('should configure $disconnect route', async () => {
140
+ const appDefinition = {
141
+ websockets: {
142
+ enable: true,
143
+ },
144
+ };
145
+
146
+ const result = await websocketBuilder.build(appDefinition, {});
147
+
148
+ const disconnectEvent = result.functions.defaultWebsocket.events.find(
149
+ e => e.websocket?.route === '$disconnect'
150
+ );
151
+
152
+ expect(disconnectEvent).toBeDefined();
153
+ });
154
+
155
+ it('should configure all three WebSocket routes', async () => {
156
+ const appDefinition = {
157
+ websockets: {
158
+ enable: true,
159
+ },
160
+ };
161
+
162
+ const result = await websocketBuilder.build(appDefinition, {});
163
+
164
+ expect(result.functions.defaultWebsocket.events).toHaveLength(3);
165
+ });
166
+
167
+ it('should not depend on discovered resources', async () => {
168
+ const appDefinition = {
169
+ websockets: {
170
+ enable: true,
171
+ },
172
+ };
173
+
174
+ const result1 = await websocketBuilder.build(appDefinition, {});
175
+ const result2 = await websocketBuilder.build(appDefinition, { vpcId: 'vpc-123' });
176
+
177
+ expect(result1.functions).toEqual(result2.functions);
178
+ });
179
+ });
180
+
181
+ describe('getDependencies()', () => {
182
+ it('should have no dependencies', () => {
183
+ const deps = websocketBuilder.getDependencies();
184
+
185
+ expect(deps).toEqual([]);
186
+ });
187
+ });
188
+
189
+ describe('getName()', () => {
190
+ it('should return WebsocketBuilder', () => {
191
+ expect(websocketBuilder.getName()).toBe('WebsocketBuilder');
192
+ });
193
+ });
194
+ });
195
+
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Tests for VPC Discovery Service
3
+ *
4
+ * Tests VPC resource discovery with mocked cloud provider
5
+ */
6
+
7
+ const { VpcDiscovery } = require('./vpc-discovery');
8
+
9
+ describe('VpcDiscovery', () => {
10
+ let mockProvider;
11
+ let vpcDiscovery;
12
+
13
+ beforeEach(() => {
14
+ mockProvider = {
15
+ discoverVpc: jest.fn(),
16
+ getName: jest.fn().mockReturnValue('aws'),
17
+ };
18
+ vpcDiscovery = new VpcDiscovery(mockProvider);
19
+ });
20
+
21
+ describe('discover()', () => {
22
+ it('should delegate to provider and transform results', async () => {
23
+ const mockProviderResponse = {
24
+ vpcId: 'vpc-123456',
25
+ vpcCidr: '172.31.0.0/16',
26
+ subnets: [
27
+ {
28
+ SubnetId: 'subnet-private1',
29
+ MapPublicIpOnLaunch: false,
30
+ AvailabilityZone: 'us-east-1a',
31
+ },
32
+ {
33
+ SubnetId: 'subnet-private2',
34
+ MapPublicIpOnLaunch: false,
35
+ AvailabilityZone: 'us-east-1b',
36
+ },
37
+ {
38
+ SubnetId: 'subnet-public1',
39
+ MapPublicIpOnLaunch: true,
40
+ AvailabilityZone: 'us-east-1a',
41
+ },
42
+ ],
43
+ securityGroups: [
44
+ { GroupId: 'sg-default', GroupName: 'default' },
45
+ { GroupId: 'sg-custom', GroupName: 'custom' },
46
+ ],
47
+ routeTables: [
48
+ {
49
+ RouteTableId: 'rtb-123',
50
+ Associations: [{ Main: true }],
51
+ },
52
+ ],
53
+ natGateways: [],
54
+ internetGateways: [
55
+ { InternetGatewayId: 'igw-123' },
56
+ ],
57
+ };
58
+
59
+ mockProvider.discoverVpc.mockResolvedValue(mockProviderResponse);
60
+
61
+ const result = await vpcDiscovery.discover({ vpcId: 'vpc-123456' });
62
+
63
+ expect(mockProvider.discoverVpc).toHaveBeenCalledWith({ vpcId: 'vpc-123456' });
64
+ expect(result.defaultVpcId).toBe('vpc-123456');
65
+ expect(result.vpcCidr).toBe('172.31.0.0/16');
66
+ expect(result.privateSubnetId1).toBe('subnet-private1');
67
+ expect(result.privateSubnetId2).toBe('subnet-private2');
68
+ expect(result.publicSubnetId).toBe('subnet-public1');
69
+ expect(result.publicSubnetId1).toBe('subnet-public1');
70
+ expect(result.defaultSecurityGroupId).toBe('sg-default');
71
+ expect(result.defaultRouteTableId).toBe('rtb-123');
72
+ expect(result.internetGatewayId).toBe('igw-123');
73
+ });
74
+
75
+ it('should handle VPC with only private subnets', async () => {
76
+ mockProvider.discoverVpc.mockResolvedValue({
77
+ vpcId: 'vpc-123',
78
+ vpcCidr: '10.0.0.0/16',
79
+ subnets: [
80
+ { SubnetId: 'subnet-1', MapPublicIpOnLaunch: false },
81
+ ],
82
+ securityGroups: [],
83
+ routeTables: [],
84
+ natGateways: [],
85
+ internetGateways: [],
86
+ });
87
+
88
+ const result = await vpcDiscovery.discover({});
89
+
90
+ expect(result.privateSubnetId1).toBe('subnet-1');
91
+ expect(result.privateSubnetId2).toBeUndefined();
92
+ expect(result.publicSubnetId).toBeUndefined();
93
+ });
94
+
95
+ it('should handle NAT gateway discovery', async () => {
96
+ mockProvider.discoverVpc.mockResolvedValue({
97
+ vpcId: 'vpc-123',
98
+ vpcCidr: '10.0.0.0/16',
99
+ subnets: [
100
+ { SubnetId: 'subnet-public', MapPublicIpOnLaunch: true },
101
+ { SubnetId: 'subnet-private', MapPublicIpOnLaunch: false },
102
+ ],
103
+ securityGroups: [],
104
+ routeTables: [],
105
+ natGateways: [
106
+ {
107
+ NatGatewayId: 'nat-123',
108
+ State: 'available',
109
+ SubnetId: 'subnet-public',
110
+ NatGatewayAddresses: [
111
+ { AllocationId: 'eipalloc-456' },
112
+ ],
113
+ },
114
+ ],
115
+ internetGateways: [],
116
+ });
117
+
118
+ const result = await vpcDiscovery.discover({});
119
+
120
+ expect(result.existingNatGatewayId).toBe('nat-123');
121
+ expect(result.natGatewayInPrivateSubnet).toBe(false);
122
+ expect(result.existingElasticIpAllocationId).toBe('eipalloc-456');
123
+ });
124
+
125
+ it('should detect NAT gateway in private subnet (configuration error)', async () => {
126
+ mockProvider.discoverVpc.mockResolvedValue({
127
+ vpcId: 'vpc-123',
128
+ vpcCidr: '10.0.0.0/16',
129
+ subnets: [
130
+ { SubnetId: 'subnet-private', MapPublicIpOnLaunch: false },
131
+ ],
132
+ securityGroups: [],
133
+ routeTables: [],
134
+ natGateways: [
135
+ {
136
+ NatGatewayId: 'nat-misplaced',
137
+ State: 'available',
138
+ SubnetId: 'subnet-private',
139
+ NatGatewayAddresses: [],
140
+ },
141
+ ],
142
+ internetGateways: [],
143
+ });
144
+
145
+ const result = await vpcDiscovery.discover({});
146
+
147
+ expect(result.existingNatGatewayId).toBe('nat-misplaced');
148
+ expect(result.natGatewayInPrivateSubnet).toBe(true);
149
+ });
150
+
151
+ it('should ignore NAT gateways that are not available', async () => {
152
+ mockProvider.discoverVpc.mockResolvedValue({
153
+ vpcId: 'vpc-123',
154
+ vpcCidr: '10.0.0.0/16',
155
+ subnets: [],
156
+ securityGroups: [],
157
+ routeTables: [],
158
+ natGateways: [
159
+ {
160
+ NatGatewayId: 'nat-pending',
161
+ State: 'pending',
162
+ SubnetId: 'subnet-public',
163
+ },
164
+ {
165
+ NatGatewayId: 'nat-failed',
166
+ State: 'failed',
167
+ SubnetId: 'subnet-public',
168
+ },
169
+ ],
170
+ internetGateways: [],
171
+ });
172
+
173
+ const result = await vpcDiscovery.discover({});
174
+
175
+ expect(result.existingNatGatewayId).toBeUndefined();
176
+ });
177
+
178
+ it('should handle discovery errors gracefully', async () => {
179
+ mockProvider.discoverVpc.mockRejectedValue(new Error('AWS API Error'));
180
+
181
+ const result = await vpcDiscovery.discover({});
182
+
183
+ expect(result.defaultVpcId).toBeNull();
184
+ expect(result.vpcCidr).toBeNull();
185
+ expect(result.privateSubnetId1).toBeNull();
186
+ });
187
+
188
+ it('should find default route table from main association', async () => {
189
+ mockProvider.discoverVpc.mockResolvedValue({
190
+ vpcId: 'vpc-123',
191
+ vpcCidr: '10.0.0.0/16',
192
+ subnets: [],
193
+ securityGroups: [],
194
+ routeTables: [
195
+ {
196
+ RouteTableId: 'rtb-custom',
197
+ Associations: [{ Main: false }],
198
+ },
199
+ {
200
+ RouteTableId: 'rtb-main',
201
+ Associations: [{ Main: true }],
202
+ },
203
+ ],
204
+ natGateways: [],
205
+ internetGateways: [],
206
+ });
207
+
208
+ const result = await vpcDiscovery.discover({});
209
+
210
+ expect(result.defaultRouteTableId).toBe('rtb-main');
211
+ expect(result.privateRouteTableId).toBe('rtb-main');
212
+ });
213
+
214
+ it('should handle multiple public subnets', async () => {
215
+ mockProvider.discoverVpc.mockResolvedValue({
216
+ vpcId: 'vpc-123',
217
+ vpcCidr: '10.0.0.0/16',
218
+ subnets: [
219
+ { SubnetId: 'subnet-public-1', MapPublicIpOnLaunch: true },
220
+ { SubnetId: 'subnet-public-2', MapPublicIpOnLaunch: true },
221
+ ],
222
+ securityGroups: [],
223
+ routeTables: [],
224
+ natGateways: [],
225
+ internetGateways: [],
226
+ });
227
+
228
+ const result = await vpcDiscovery.discover({});
229
+
230
+ expect(result.publicSubnetId).toBe('subnet-public-1');
231
+ expect(result.publicSubnetId1).toBe('subnet-public-1');
232
+ expect(result.publicSubnetId2).toBe('subnet-public-2');
233
+ });
234
+
235
+ it('should pass config to provider', async () => {
236
+ mockProvider.discoverVpc.mockResolvedValue({
237
+ vpcId: 'vpc-custom',
238
+ subnets: [],
239
+ securityGroups: [],
240
+ routeTables: [],
241
+ natGateways: [],
242
+ internetGateways: [],
243
+ });
244
+
245
+ const config = {
246
+ vpcId: 'vpc-custom',
247
+ serviceName: 'test-service',
248
+ stage: 'prod',
249
+ };
250
+
251
+ await vpcDiscovery.discover(config);
252
+
253
+ expect(mockProvider.discoverVpc).toHaveBeenCalledWith(config);
254
+ });
255
+ });
256
+ });
257
+
@@ -0,0 +1,79 @@
1
+ /**
2
+ * SSM Parameter Store Builder
3
+ *
4
+ * Domain Layer - Hexagonal Architecture
5
+ *
6
+ * Responsible for:
7
+ * - Configuring IAM permissions for SSM Parameter Store access
8
+ * - Setting up SSM parameter references for Lambda functions
9
+ */
10
+
11
+ const { InfrastructureBuilder, ValidationResult } = require('../shared/base-builder');
12
+
13
+ class SsmBuilder extends InfrastructureBuilder {
14
+ constructor() {
15
+ super();
16
+ this.name = 'SsmBuilder';
17
+ }
18
+
19
+ shouldExecute(appDefinition) {
20
+ // Skip SSM in local mode (when FRIGG_SKIP_AWS_DISCOVERY is set)
21
+ // SSM Parameter Store is an AWS-specific service that should only be used in production
22
+ if (process.env.FRIGG_SKIP_AWS_DISCOVERY === 'true') {
23
+ return false;
24
+ }
25
+
26
+ return appDefinition.ssm?.enable === true;
27
+ }
28
+
29
+ validate(appDefinition) {
30
+ const result = new ValidationResult();
31
+
32
+ if (!appDefinition.ssm) {
33
+ result.addError('SSM configuration is missing');
34
+ return result;
35
+ }
36
+
37
+ // Validate parameters if provided
38
+ if (appDefinition.ssm.parameters) {
39
+ if (typeof appDefinition.ssm.parameters !== 'object') {
40
+ result.addError('ssm.parameters must be an object');
41
+ }
42
+ }
43
+
44
+ return result;
45
+ }
46
+
47
+ /**
48
+ * Build SSM configuration
49
+ */
50
+ async build(appDefinition, discoveredResources) {
51
+ console.log(`\n[${this.name}] Configuring SSM Parameter Store...`);
52
+
53
+ const result = {
54
+ iamStatements: [],
55
+ environment: {},
56
+ };
57
+
58
+ // Add IAM permissions for SSM Parameter Store
59
+ result.iamStatements.push({
60
+ Effect: 'Allow',
61
+ Action: [
62
+ 'ssm:GetParameter',
63
+ 'ssm:GetParameters',
64
+ 'ssm:GetParametersByPath',
65
+ ],
66
+ Resource: {
67
+ 'Fn::Sub': 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*',
68
+ },
69
+ });
70
+
71
+ console.log(' ✅ SSM Parameter Store IAM permissions added');
72
+ console.log(`[${this.name}] ✅ SSM configuration completed`);
73
+
74
+ return result;
75
+ }
76
+ }
77
+
78
+ module.exports = { SsmBuilder };
79
+