cucumberstudio-mcp 1.1.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 (130) hide show
  1. package/.env.example +36 -0
  2. package/.github/workflows/pr-checks.yml +41 -0
  3. package/.github/workflows/release.yml +194 -0
  4. package/.prettierignore +26 -0
  5. package/.prettierrc +14 -0
  6. package/CLAUDE.md +140 -0
  7. package/Dockerfile +50 -0
  8. package/Dockerfile.dev +31 -0
  9. package/LICENSE +21 -0
  10. package/README.md +395 -0
  11. package/build/api/client.d.ts +49 -0
  12. package/build/api/client.d.ts.map +1 -0
  13. package/build/api/client.js +204 -0
  14. package/build/api/client.js.map +1 -0
  15. package/build/api/types.d.ts +113 -0
  16. package/build/api/types.d.ts.map +1 -0
  17. package/build/api/types.js +2 -0
  18. package/build/api/types.js.map +1 -0
  19. package/build/config/settings.d.ts +123 -0
  20. package/build/config/settings.d.ts.map +1 -0
  21. package/build/config/settings.js +97 -0
  22. package/build/config/settings.js.map +1 -0
  23. package/build/constants.d.ts +16 -0
  24. package/build/constants.d.ts.map +1 -0
  25. package/build/constants.js +24 -0
  26. package/build/constants.js.map +1 -0
  27. package/build/generated/version.d.ts +3 -0
  28. package/build/generated/version.d.ts.map +1 -0
  29. package/build/generated/version.js +5 -0
  30. package/build/generated/version.js.map +1 -0
  31. package/build/index.d.ts +3 -0
  32. package/build/index.d.ts.map +1 -0
  33. package/build/index.js +81 -0
  34. package/build/index.js.map +1 -0
  35. package/build/mcp-server.d.ts +6 -0
  36. package/build/mcp-server.d.ts.map +1 -0
  37. package/build/mcp-server.js +263 -0
  38. package/build/mcp-server.js.map +1 -0
  39. package/build/tools/action-words.d.ts +18 -0
  40. package/build/tools/action-words.d.ts.map +1 -0
  41. package/build/tools/action-words.js +191 -0
  42. package/build/tools/action-words.js.map +1 -0
  43. package/build/tools/projects.d.ts +19 -0
  44. package/build/tools/projects.d.ts.map +1 -0
  45. package/build/tools/projects.js +123 -0
  46. package/build/tools/projects.js.map +1 -0
  47. package/build/tools/scenarios.d.ts +18 -0
  48. package/build/tools/scenarios.d.ts.map +1 -0
  49. package/build/tools/scenarios.js +194 -0
  50. package/build/tools/scenarios.js.map +1 -0
  51. package/build/tools/test-runs.d.ts +21 -0
  52. package/build/tools/test-runs.d.ts.map +1 -0
  53. package/build/tools/test-runs.js +324 -0
  54. package/build/tools/test-runs.js.map +1 -0
  55. package/build/transports/http.d.ts +38 -0
  56. package/build/transports/http.d.ts.map +1 -0
  57. package/build/transports/http.js +381 -0
  58. package/build/transports/http.js.map +1 -0
  59. package/build/transports/index.d.ts +22 -0
  60. package/build/transports/index.d.ts.map +1 -0
  61. package/build/transports/index.js +10 -0
  62. package/build/transports/index.js.map +1 -0
  63. package/build/transports/stdio.d.ts +13 -0
  64. package/build/transports/stdio.d.ts.map +1 -0
  65. package/build/transports/stdio.js +24 -0
  66. package/build/transports/stdio.js.map +1 -0
  67. package/build/utils/errors.d.ts +10 -0
  68. package/build/utils/errors.d.ts.map +1 -0
  69. package/build/utils/errors.js +35 -0
  70. package/build/utils/errors.js.map +1 -0
  71. package/build/utils/logger-constants.d.ts +15 -0
  72. package/build/utils/logger-constants.d.ts.map +1 -0
  73. package/build/utils/logger-constants.js +16 -0
  74. package/build/utils/logger-constants.js.map +1 -0
  75. package/build/utils/logger.d.ts +55 -0
  76. package/build/utils/logger.d.ts.map +1 -0
  77. package/build/utils/logger.js +113 -0
  78. package/build/utils/logger.js.map +1 -0
  79. package/build/utils/validation.d.ts +89 -0
  80. package/build/utils/validation.d.ts.map +1 -0
  81. package/build/utils/validation.js +78 -0
  82. package/build/utils/validation.js.map +1 -0
  83. package/docker-compose.yml +20 -0
  84. package/eslint.config.js +97 -0
  85. package/package.json +92 -0
  86. package/scripts/generate-version.js +31 -0
  87. package/src/api/client.ts +286 -0
  88. package/src/api/types.ts +137 -0
  89. package/src/config/settings.ts +113 -0
  90. package/src/constants.ts +29 -0
  91. package/src/index.ts +99 -0
  92. package/src/mcp-server.ts +342 -0
  93. package/src/tools/action-words.ts +240 -0
  94. package/src/tools/projects.ts +144 -0
  95. package/src/tools/scenarios.ts +231 -0
  96. package/src/tools/test-runs.ts +400 -0
  97. package/src/transports/http.ts +467 -0
  98. package/src/transports/index.ts +26 -0
  99. package/src/transports/stdio.ts +28 -0
  100. package/src/utils/errors.ts +45 -0
  101. package/src/utils/logger-constants.ts +18 -0
  102. package/src/utils/logger.ts +150 -0
  103. package/src/utils/validation.ts +94 -0
  104. package/test/api/client-with-msw.test.ts +122 -0
  105. package/test/api/client.test.ts +326 -0
  106. package/test/api/types.test.ts +88 -0
  107. package/test/config/settings.test.ts +204 -0
  108. package/test/mocks/data/action-words.ts +40 -0
  109. package/test/mocks/data/index.ts +13 -0
  110. package/test/mocks/data/projects.ts +38 -0
  111. package/test/mocks/data/scenarios.ts +53 -0
  112. package/test/mocks/data/test-runs.ts +101 -0
  113. package/test/mocks/handlers/action-words.ts +52 -0
  114. package/test/mocks/handlers/index.ts +10 -0
  115. package/test/mocks/handlers/projects.ts +45 -0
  116. package/test/mocks/handlers/scenarios.ts +72 -0
  117. package/test/mocks/handlers/test-runs.ts +106 -0
  118. package/test/mocks/server.ts +26 -0
  119. package/test/setup/vitest.setup.ts +18 -0
  120. package/test/tools/coverage-boost.test.ts +252 -0
  121. package/test/tools/projects.test.ts +290 -0
  122. package/test/tools/tools-basic.test.ts +146 -0
  123. package/test/transports/http-basic.test.ts +87 -0
  124. package/test/transports/http-simple.test.ts +33 -0
  125. package/test/transports/stdio.test.ts +73 -0
  126. package/test/utils/errors.test.ts +117 -0
  127. package/test/utils/validation.test.ts +261 -0
  128. package/tsconfig.build.json +8 -0
  129. package/tsconfig.json +27 -0
  130. package/vitest.config.ts +43 -0
@@ -0,0 +1,117 @@
1
+ import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'
2
+ import { describe, it, expect } from 'vitest'
3
+
4
+ import { createMcpError, safeExecute } from '@/utils/errors.js'
5
+
6
+ describe('errors utilities', () => {
7
+ describe('createMcpError', () => {
8
+ it('should return McpError as-is', () => {
9
+ const originalError = new McpError(ErrorCode.InvalidParams, 'Test error')
10
+ const result = createMcpError(originalError)
11
+
12
+ expect(result).toBe(originalError)
13
+ })
14
+
15
+ it('should convert CucumberStudioApiError to McpError', () => {
16
+ const apiError = new Error('API failed')
17
+ apiError.name = 'CucumberStudioApiError'
18
+
19
+ const result = createMcpError(apiError, 'test context')
20
+
21
+ expect(result).toBeInstanceOf(McpError)
22
+ expect(result.code).toBe(ErrorCode.InternalError)
23
+ expect(result.message).toContain('Cucumber Studio API error (test context): API failed')
24
+ })
25
+
26
+ it('should convert ZodError to McpError', () => {
27
+ const zodError = new Error('Validation failed')
28
+ zodError.name = 'ZodError'
29
+
30
+ const result = createMcpError(zodError)
31
+
32
+ expect(result).toBeInstanceOf(McpError)
33
+ expect(result.code).toBe(ErrorCode.InvalidParams)
34
+ expect(result.message).toContain('Validation error: Validation failed')
35
+ })
36
+
37
+ it('should convert generic Error to McpError', () => {
38
+ const genericError = new Error('Something went wrong')
39
+
40
+ const result = createMcpError(genericError, 'operation')
41
+
42
+ expect(result).toBeInstanceOf(McpError)
43
+ expect(result.code).toBe(ErrorCode.InternalError)
44
+ expect(result.message).toContain('operation: Something went wrong')
45
+ })
46
+
47
+ it('should handle unknown error types', () => {
48
+ const unknownError = 'string error'
49
+
50
+ const result = createMcpError(unknownError)
51
+
52
+ expect(result).toBeInstanceOf(McpError)
53
+ expect(result.code).toBe(ErrorCode.InternalError)
54
+ expect(result.message).toContain('Unknown error: string error')
55
+ })
56
+
57
+ it('should handle context parameter', () => {
58
+ const error = new Error('Test error')
59
+
60
+ const result = createMcpError(error, 'test context')
61
+
62
+ expect(result.message).toContain('test context: Test error')
63
+ })
64
+ })
65
+
66
+ describe('safeExecute', () => {
67
+ it('should return result on successful execution', async () => {
68
+ const operation = async () => 'success'
69
+
70
+ const result = await safeExecute(operation)
71
+
72
+ expect(result).toBe('success')
73
+ })
74
+
75
+ it('should throw McpError on operation failure', async () => {
76
+ const operation = async () => {
77
+ throw new Error('Operation failed')
78
+ }
79
+
80
+ await expect(safeExecute(operation, 'test operation')).rejects.toThrow(McpError)
81
+ })
82
+
83
+ it('should preserve context in error', async () => {
84
+ const operation = async () => {
85
+ throw new Error('Operation failed')
86
+ }
87
+
88
+ try {
89
+ await safeExecute(operation, 'test operation')
90
+ } catch (error) {
91
+ expect(error).toBeInstanceOf(McpError)
92
+ expect((error as McpError).message).toContain('test operation: Operation failed')
93
+ }
94
+ })
95
+
96
+ it('should handle async operations that return promises', async () => {
97
+ const operation = () => Promise.resolve(42)
98
+
99
+ const result = await safeExecute(operation)
100
+
101
+ expect(result).toBe(42)
102
+ })
103
+
104
+ it('should handle operations that throw McpError directly', async () => {
105
+ const originalError = new McpError(ErrorCode.InvalidParams, 'Invalid input')
106
+ const operation = async () => {
107
+ throw originalError
108
+ }
109
+
110
+ try {
111
+ await safeExecute(operation)
112
+ } catch (error) {
113
+ expect(error).toBe(originalError)
114
+ }
115
+ })
116
+ })
117
+ })
@@ -0,0 +1,261 @@
1
+ import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'
2
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest'
3
+
4
+ import {
5
+ ProjectIdSchema,
6
+ ScenarioIdSchema,
7
+ ActionWordIdSchema,
8
+ FolderIdSchema,
9
+ TestRunIdSchema,
10
+ BuildIdSchema,
11
+ PaginationSchema,
12
+ FilterSchema,
13
+ ListParamsSchema,
14
+ convertToApiParams,
15
+ validateInput,
16
+ validateEnvironment,
17
+ } from '../../src/utils/validation.js'
18
+
19
+ describe('validation utilities', () => {
20
+ describe('schemas', () => {
21
+ it('should validate project ID', () => {
22
+ expect(ProjectIdSchema.parse('project-123')).toBe('project-123')
23
+ expect(() => ProjectIdSchema.parse('')).toThrow()
24
+ expect(() => ProjectIdSchema.parse(null)).toThrow()
25
+ })
26
+
27
+ it('should validate scenario ID', () => {
28
+ expect(ScenarioIdSchema.parse('scenario-456')).toBe('scenario-456')
29
+ expect(() => ScenarioIdSchema.parse('')).toThrow()
30
+ })
31
+
32
+ it('should validate action word ID', () => {
33
+ expect(ActionWordIdSchema.parse('action-789')).toBe('action-789')
34
+ expect(() => ActionWordIdSchema.parse('')).toThrow()
35
+ })
36
+
37
+ it('should validate folder ID', () => {
38
+ expect(FolderIdSchema.parse('folder-101')).toBe('folder-101')
39
+ expect(() => FolderIdSchema.parse('')).toThrow()
40
+ })
41
+
42
+ it('should validate test run ID', () => {
43
+ expect(TestRunIdSchema.parse('testrun-202')).toBe('testrun-202')
44
+ expect(() => TestRunIdSchema.parse('')).toThrow()
45
+ })
46
+
47
+ it('should validate build ID', () => {
48
+ expect(BuildIdSchema.parse('build-303')).toBe('build-303')
49
+ expect(() => BuildIdSchema.parse('')).toThrow()
50
+ })
51
+
52
+ it('should validate pagination schema', () => {
53
+ expect(PaginationSchema.parse({ page: 1, pageSize: 10 })).toEqual({ page: 1, pageSize: 10 })
54
+ expect(PaginationSchema.parse({})).toEqual({})
55
+ expect(PaginationSchema.parse(undefined)).toBeUndefined()
56
+ expect(() => PaginationSchema.parse({ page: 0 })).toThrow()
57
+ expect(() => PaginationSchema.parse({ pageSize: 101 })).toThrow()
58
+ })
59
+
60
+ it('should validate filter schema', () => {
61
+ expect(FilterSchema.parse({ name: 'test', tags: 'tag1,tag2' })).toEqual({ name: 'test', tags: 'tag1,tag2' })
62
+ expect(FilterSchema.parse({})).toEqual({})
63
+ expect(FilterSchema.parse(undefined)).toBeUndefined()
64
+ })
65
+
66
+ it('should validate list params schema', () => {
67
+ const validParams = {
68
+ pagination: { page: 1, pageSize: 20 },
69
+ filter: { name: 'test' },
70
+ }
71
+ expect(ListParamsSchema.parse(validParams)).toEqual(validParams)
72
+ expect(ListParamsSchema.parse({})).toEqual({})
73
+ expect(ListParamsSchema.parse(undefined)).toBeUndefined()
74
+ })
75
+ })
76
+
77
+ describe('convertToApiParams', () => {
78
+ it('should convert pagination parameters', () => {
79
+ const params = {
80
+ pagination: { page: 2, pageSize: 25 },
81
+ }
82
+
83
+ const result = convertToApiParams(params)
84
+
85
+ expect(result).toEqual({
86
+ 'page[number]': 2,
87
+ 'page[size]': 25,
88
+ })
89
+ })
90
+
91
+ it('should convert filter parameters', () => {
92
+ const params = {
93
+ filter: { name: 'test-scenario', tags: 'smoke,regression' },
94
+ }
95
+
96
+ const result = convertToApiParams(params)
97
+
98
+ expect(result).toEqual({
99
+ 'filter[name]': 'test-scenario',
100
+ 'filter[tags]': 'smoke,regression',
101
+ })
102
+ })
103
+
104
+ it('should convert combined parameters', () => {
105
+ const params = {
106
+ pagination: { page: 3, pageSize: 50 },
107
+ filter: { name: 'api-test', tags: 'api' },
108
+ }
109
+
110
+ const result = convertToApiParams(params)
111
+
112
+ expect(result).toEqual({
113
+ 'page[number]': 3,
114
+ 'page[size]': 50,
115
+ 'filter[name]': 'api-test',
116
+ 'filter[tags]': 'api',
117
+ })
118
+ })
119
+
120
+ it('should handle undefined parameters', () => {
121
+ const result = convertToApiParams()
122
+ expect(result).toEqual({})
123
+ })
124
+
125
+ it('should handle empty parameters', () => {
126
+ const result = convertToApiParams({})
127
+ expect(result).toEqual({})
128
+ })
129
+
130
+ it('should handle partial parameters', () => {
131
+ const params = {
132
+ pagination: { page: 1 },
133
+ filter: { tags: 'integration' },
134
+ }
135
+
136
+ const result = convertToApiParams(params)
137
+
138
+ expect(result).toEqual({
139
+ 'page[number]': 1,
140
+ 'filter[tags]': 'integration',
141
+ })
142
+ })
143
+ })
144
+
145
+ describe('validateInput', () => {
146
+ it('should return parsed input for valid data', () => {
147
+ const result = validateInput(ProjectIdSchema, 'valid-project-id')
148
+ expect(result).toBe('valid-project-id')
149
+ })
150
+
151
+ it('should throw McpError for invalid data', () => {
152
+ expect(() => validateInput(ProjectIdSchema, '')).toThrow(McpError)
153
+ expect(() => validateInput(ProjectIdSchema, null)).toThrow(McpError)
154
+ })
155
+
156
+ it('should include context in error message', () => {
157
+ try {
158
+ validateInput(ProjectIdSchema, '', 'test operation')
159
+ } catch (error) {
160
+ expect(error).toBeInstanceOf(McpError)
161
+ expect((error as McpError).code).toBe(ErrorCode.InvalidParams)
162
+ expect((error as McpError).message).toContain('test operation')
163
+ }
164
+ })
165
+
166
+ it('should handle complex schema validation', () => {
167
+ const complexInput = {
168
+ pagination: { page: 1, pageSize: 10 },
169
+ filter: { name: 'test' },
170
+ }
171
+
172
+ const result = validateInput(ListParamsSchema, complexInput)
173
+ expect(result).toEqual(complexInput)
174
+ })
175
+
176
+ it('should provide detailed error messages for schema violations', () => {
177
+ try {
178
+ validateInput(PaginationSchema, { page: 0, pageSize: 101 })
179
+ } catch (error) {
180
+ expect(error).toBeInstanceOf(McpError)
181
+ const mcpError = error as McpError
182
+ expect(mcpError.message).toContain('page')
183
+ expect(mcpError.message).toContain('pageSize')
184
+ }
185
+ })
186
+ })
187
+
188
+ describe('validateEnvironment', () => {
189
+ let originalEnv: NodeJS.ProcessEnv
190
+
191
+ beforeEach(() => {
192
+ originalEnv = { ...process.env }
193
+ })
194
+
195
+ afterEach(() => {
196
+ process.env = originalEnv
197
+ })
198
+
199
+ it('should pass when all required environment variables are present', () => {
200
+ process.env.CUCUMBERSTUDIO_ACCESS_TOKEN = 'test-token'
201
+ process.env.CUCUMBERSTUDIO_CLIENT_ID = 'test-client'
202
+ process.env.CUCUMBERSTUDIO_UID = 'test-uid'
203
+
204
+ expect(() => validateEnvironment()).not.toThrow()
205
+ })
206
+
207
+ it('should throw McpError when access token is missing', () => {
208
+ delete process.env.CUCUMBERSTUDIO_ACCESS_TOKEN
209
+ process.env.CUCUMBERSTUDIO_CLIENT_ID = 'test-client'
210
+ process.env.CUCUMBERSTUDIO_UID = 'test-uid'
211
+
212
+ expect(() => validateEnvironment()).toThrow(McpError)
213
+ expect(() => validateEnvironment()).toThrow(/CUCUMBERSTUDIO_ACCESS_TOKEN/)
214
+ })
215
+
216
+ it('should throw McpError when client ID is missing', () => {
217
+ process.env.CUCUMBERSTUDIO_ACCESS_TOKEN = 'test-token'
218
+ delete process.env.CUCUMBERSTUDIO_CLIENT_ID
219
+ process.env.CUCUMBERSTUDIO_UID = 'test-uid'
220
+
221
+ expect(() => validateEnvironment()).toThrow(McpError)
222
+ expect(() => validateEnvironment()).toThrow(/CUCUMBERSTUDIO_CLIENT_ID/)
223
+ })
224
+
225
+ it('should throw McpError when UID is missing', () => {
226
+ process.env.CUCUMBERSTUDIO_ACCESS_TOKEN = 'test-token'
227
+ process.env.CUCUMBERSTUDIO_CLIENT_ID = 'test-client'
228
+ delete process.env.CUCUMBERSTUDIO_UID
229
+
230
+ expect(() => validateEnvironment()).toThrow(McpError)
231
+ expect(() => validateEnvironment()).toThrow(/CUCUMBERSTUDIO_UID/)
232
+ })
233
+
234
+ it('should throw McpError when multiple variables are missing', () => {
235
+ delete process.env.CUCUMBERSTUDIO_ACCESS_TOKEN
236
+ delete process.env.CUCUMBERSTUDIO_CLIENT_ID
237
+ delete process.env.CUCUMBERSTUDIO_UID
238
+
239
+ try {
240
+ validateEnvironment()
241
+ } catch (error) {
242
+ expect(error).toBeInstanceOf(McpError)
243
+ const mcpError = error as McpError
244
+ expect(mcpError.message).toContain('CUCUMBERSTUDIO_ACCESS_TOKEN')
245
+ expect(mcpError.message).toContain('CUCUMBERSTUDIO_CLIENT_ID')
246
+ expect(mcpError.message).toContain('CUCUMBERSTUDIO_UID')
247
+ }
248
+ })
249
+
250
+ it('should have correct error code', () => {
251
+ delete process.env.CUCUMBERSTUDIO_ACCESS_TOKEN
252
+
253
+ try {
254
+ validateEnvironment()
255
+ } catch (error) {
256
+ expect(error).toBeInstanceOf(McpError)
257
+ expect((error as McpError).code).toBe(ErrorCode.InvalidRequest)
258
+ }
259
+ })
260
+ })
261
+ })
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src"
5
+ },
6
+ "include": ["src/**/*"],
7
+ "exclude": ["node_modules", "build", "dist", "test/**/*"]
8
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./build",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "sourceMap": true,
15
+ "resolveJsonModule": true,
16
+ "allowSyntheticDefaultImports": true,
17
+ "experimentalDecorators": true,
18
+ "emitDecoratorMetadata": true,
19
+ "baseUrl": ".",
20
+ "paths": {
21
+ "@/*": ["src/*"],
22
+ "@test/*": ["test/*"]
23
+ }
24
+ },
25
+ "include": ["src/**/*", "test/**/*"],
26
+ "exclude": ["node_modules", "build", "dist"]
27
+ }
@@ -0,0 +1,43 @@
1
+ import { defineConfig } from 'vitest/config'
2
+ import tsconfigPaths from 'vite-tsconfig-paths'
3
+
4
+ export default defineConfig({
5
+ plugins: [tsconfigPaths()],
6
+ test: {
7
+ environment: 'node',
8
+ globals: true,
9
+ restoreMocks: true,
10
+ clearMocks: true,
11
+ setupFiles: ['./test/setup/vitest.setup.ts'],
12
+ include: ['test/**/*.test.ts'],
13
+ exclude: ['node_modules', 'build', 'dist'],
14
+ reporters: ['default'],
15
+ coverage: {
16
+ provider: 'v8',
17
+ include: ['src/**/*.ts'],
18
+ exclude: [
19
+ 'src/**/*.test.ts',
20
+ 'src/**/*.spec.ts',
21
+ 'src/**/index.ts',
22
+ 'node_modules/',
23
+ 'build/',
24
+ 'dist/',
25
+ 'test/',
26
+ '**/*.test.ts',
27
+ '**/*.spec.ts',
28
+ '**/*.d.ts',
29
+ 'vitest.config.ts',
30
+ 'eslint.config.js',
31
+ '.prettierrc'
32
+ ],
33
+ thresholds: {
34
+ global: {
35
+ branches: 85,
36
+ functions: 85,
37
+ lines: 85,
38
+ statements: 85
39
+ }
40
+ }
41
+ }
42
+ }
43
+ })