gitlab-mcp 1.1.0 → 1.2.1

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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +12 -1
  3. package/dist/config/dotenv.d.ts +2 -0
  4. package/dist/config/dotenv.js +40 -0
  5. package/dist/config/dotenv.js.map +1 -0
  6. package/dist/config/env.d.ts +55 -0
  7. package/dist/config/env.js +164 -0
  8. package/dist/config/env.js.map +1 -0
  9. package/dist/http-app.d.ts +45 -0
  10. package/dist/http-app.js +550 -0
  11. package/dist/http-app.js.map +1 -0
  12. package/dist/http.d.ts +2 -0
  13. package/dist/http.js +65 -0
  14. package/dist/http.js.map +1 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +65 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/lib/auth-context.d.ts +9 -0
  19. package/dist/lib/auth-context.js +9 -0
  20. package/dist/lib/auth-context.js.map +1 -0
  21. package/dist/lib/gitlab-client.d.ts +331 -0
  22. package/dist/lib/gitlab-client.js +1025 -0
  23. package/dist/lib/gitlab-client.js.map +1 -0
  24. package/dist/lib/logger.d.ts +2 -0
  25. package/dist/lib/logger.js +13 -0
  26. package/dist/lib/logger.js.map +1 -0
  27. package/dist/lib/network.d.ts +3 -0
  28. package/dist/lib/network.js +38 -0
  29. package/dist/lib/network.js.map +1 -0
  30. package/dist/lib/oauth.d.ts +29 -0
  31. package/dist/lib/oauth.js +220 -0
  32. package/dist/lib/oauth.js.map +1 -0
  33. package/dist/lib/output.d.ts +14 -0
  34. package/dist/lib/output.js +38 -0
  35. package/dist/lib/output.js.map +1 -0
  36. package/dist/lib/policy.d.ts +25 -0
  37. package/dist/lib/policy.js +48 -0
  38. package/dist/lib/policy.js.map +1 -0
  39. package/dist/lib/request-runtime.d.ts +26 -0
  40. package/dist/lib/request-runtime.js +323 -0
  41. package/dist/lib/request-runtime.js.map +1 -0
  42. package/dist/lib/sanitize.d.ts +1 -0
  43. package/dist/lib/sanitize.js +21 -0
  44. package/dist/lib/sanitize.js.map +1 -0
  45. package/dist/lib/session-capacity.d.ts +8 -0
  46. package/dist/lib/session-capacity.js +7 -0
  47. package/dist/lib/session-capacity.js.map +1 -0
  48. package/dist/server/build-server.d.ts +3 -0
  49. package/dist/server/build-server.js +13 -0
  50. package/dist/server/build-server.js.map +1 -0
  51. package/dist/tools/gitlab.d.ts +9 -0
  52. package/dist/tools/gitlab.js +2576 -0
  53. package/dist/tools/gitlab.js.map +1 -0
  54. package/dist/tools/health.d.ts +2 -0
  55. package/dist/tools/health.js +21 -0
  56. package/dist/tools/health.js.map +1 -0
  57. package/dist/tools/mr-code-context.d.ts +38 -0
  58. package/dist/tools/mr-code-context.js +330 -0
  59. package/dist/tools/mr-code-context.js.map +1 -0
  60. package/{src/types/context.ts → dist/types/context.d.ts} +5 -6
  61. package/dist/types/context.js +2 -0
  62. package/dist/types/context.js.map +1 -0
  63. package/docs/architecture.md +10 -10
  64. package/docs/configuration.md +12 -7
  65. package/docs/mcp-integration-testing-best-practices.md +981 -0
  66. package/package.json +13 -1
  67. package/.dockerignore +0 -7
  68. package/.editorconfig +0 -9
  69. package/.env.example +0 -75
  70. package/.github/workflows/nodejs.yml +0 -31
  71. package/.github/workflows/npm-publish.yml +0 -31
  72. package/.husky/pre-commit +0 -1
  73. package/.nvmrc +0 -1
  74. package/.prettierrc.json +0 -6
  75. package/Dockerfile +0 -20
  76. package/docker-compose.yml +0 -10
  77. package/eslint.config.js +0 -23
  78. package/scripts/get-oauth-token.example.sh +0 -15
  79. package/src/config/env.ts +0 -171
  80. package/src/http.ts +0 -620
  81. package/src/index.ts +0 -77
  82. package/src/lib/auth-context.ts +0 -19
  83. package/src/lib/gitlab-client.ts +0 -1810
  84. package/src/lib/logger.ts +0 -17
  85. package/src/lib/network.ts +0 -45
  86. package/src/lib/oauth.ts +0 -287
  87. package/src/lib/output.ts +0 -51
  88. package/src/lib/policy.ts +0 -78
  89. package/src/lib/request-runtime.ts +0 -376
  90. package/src/lib/sanitize.ts +0 -25
  91. package/src/lib/session-capacity.ts +0 -14
  92. package/src/server/build-server.ts +0 -17
  93. package/src/tools/gitlab.ts +0 -3135
  94. package/src/tools/health.ts +0 -27
  95. package/src/tools/mr-code-context.ts +0 -473
  96. package/tests/auth-context.test.ts +0 -102
  97. package/tests/gitlab-client.test.ts +0 -672
  98. package/tests/graphql-guard.test.ts +0 -121
  99. package/tests/integration/agent-loop.integration.test.ts +0 -558
  100. package/tests/integration/server.integration.test.ts +0 -543
  101. package/tests/mr-code-context.test.ts +0 -600
  102. package/tests/oauth.test.ts +0 -43
  103. package/tests/output.test.ts +0 -186
  104. package/tests/policy.test.ts +0 -324
  105. package/tests/request-runtime.test.ts +0 -252
  106. package/tests/sanitize.test.ts +0 -123
  107. package/tests/session-capacity.test.ts +0 -49
  108. package/tests/upload-reference.test.ts +0 -88
  109. package/tsconfig.build.json +0 -11
  110. package/tsconfig.json +0 -21
  111. package/vitest.config.ts +0 -12
@@ -1,252 +0,0 @@
1
- /**
2
- * Tests for helper functions exported or used internally by request-runtime.ts.
3
- * Since some functions are private, we test them indirectly or test the module-level
4
- * exported utilities that are accessible.
5
- *
6
- * For deeper testing we extract testable logic patterns.
7
- */
8
- import { describe, expect, it } from "vitest";
9
- import * as os from "node:os";
10
- import * as path from "node:path";
11
-
12
- /**
13
- * Replicate the parseTokenOutput logic for testing.
14
- * This matches the private function in request-runtime.ts.
15
- */
16
- function parseTokenOutput(rawOutput: string): string | undefined {
17
- const output = rawOutput.trim();
18
- if (!output) {
19
- return undefined;
20
- }
21
-
22
- try {
23
- const parsed = JSON.parse(output) as Record<string, unknown>;
24
- const token =
25
- getStringField(parsed, "token") ||
26
- getStringField(parsed, "access_token") ||
27
- getStringField(parsed, "private_token");
28
- if (token) {
29
- return token;
30
- }
31
- } catch {
32
- // Plain string output is valid.
33
- }
34
-
35
- return output.split(/\r?\n/, 1)[0]?.trim() || undefined;
36
- }
37
-
38
- function getStringField(record: Record<string, unknown>, key: string): string | undefined {
39
- const value = record[key];
40
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
41
- }
42
-
43
- /**
44
- * Replicate resolveHomePath for testing.
45
- */
46
- function resolveHomePath(input?: string): string | undefined {
47
- if (!input) {
48
- return undefined;
49
- }
50
-
51
- if (input.startsWith("~/")) {
52
- return path.join(os.homedir(), input.slice(2));
53
- }
54
-
55
- return input;
56
- }
57
-
58
- /**
59
- * Replicate normalizeWarmupPath for testing.
60
- */
61
- function normalizeWarmupPath(value: string): string {
62
- const trimmed = value.trim();
63
- if (!trimmed) {
64
- return "/user";
65
- }
66
-
67
- return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
68
- }
69
-
70
- /**
71
- * Replicate resolveApiRoot for testing.
72
- */
73
- function resolveApiRoot(url: URL): string | undefined {
74
- const match = url.pathname.match(/^(.*\/api\/v4)(?:\/|$)/);
75
- return match?.[1];
76
- }
77
-
78
- /**
79
- * Replicate parseOauthScopes for testing.
80
- */
81
- function parseOauthScopes(rawScopes: string): string[] {
82
- return rawScopes
83
- .split(/[,\s]+/)
84
- .map((scope) => scope.trim())
85
- .filter((scope) => scope.length > 0);
86
- }
87
-
88
- describe("parseTokenOutput", () => {
89
- it("returns undefined for empty string", () => {
90
- expect(parseTokenOutput("")).toBeUndefined();
91
- });
92
-
93
- it("returns undefined for whitespace-only string", () => {
94
- expect(parseTokenOutput(" \n ")).toBeUndefined();
95
- });
96
-
97
- it("parses plain text token", () => {
98
- expect(parseTokenOutput("glpat-abc123")).toBe("glpat-abc123");
99
- });
100
-
101
- it("parses plain text with trailing newline", () => {
102
- expect(parseTokenOutput("glpat-abc123\n")).toBe("glpat-abc123");
103
- });
104
-
105
- it("takes first line of multi-line output", () => {
106
- expect(parseTokenOutput("glpat-abc123\nsome debug info\nmore stuff")).toBe("glpat-abc123");
107
- });
108
-
109
- it("parses JSON with token field", () => {
110
- expect(parseTokenOutput('{"token": "glpat-from-json"}')).toBe("glpat-from-json");
111
- });
112
-
113
- it("parses JSON with access_token field", () => {
114
- expect(parseTokenOutput('{"access_token": "oauth-token-123"}')).toBe("oauth-token-123");
115
- });
116
-
117
- it("parses JSON with private_token field", () => {
118
- expect(parseTokenOutput('{"private_token": "private-123"}')).toBe("private-123");
119
- });
120
-
121
- it("prefers token field over access_token", () => {
122
- expect(parseTokenOutput('{"token": "primary", "access_token": "secondary"}')).toBe("primary");
123
- });
124
-
125
- it("ignores JSON with empty token fields", () => {
126
- expect(parseTokenOutput('{"token": "", "other": "value"}')).toBe(
127
- '{"token": "", "other": "value"}'
128
- );
129
- });
130
-
131
- it("ignores JSON with whitespace-only token fields", () => {
132
- expect(parseTokenOutput('{"token": " "}')).toBe('{"token": " "}');
133
- });
134
-
135
- it("trims whitespace from parsed token values", () => {
136
- expect(parseTokenOutput('{"token": " trimmed "}')).toBe("trimmed");
137
- });
138
-
139
- it("handles JSON with no recognized token fields", () => {
140
- const input = '{"unknown_field": "some-value"}';
141
- // Falls back to first line
142
- expect(parseTokenOutput(input)).toBe(input);
143
- });
144
- });
145
-
146
- describe("resolveHomePath", () => {
147
- it("returns undefined for empty input", () => {
148
- expect(resolveHomePath("")).toBeUndefined();
149
- expect(resolveHomePath(undefined)).toBeUndefined();
150
- });
151
-
152
- it("expands ~ to home directory", () => {
153
- const result = resolveHomePath("~/some/path");
154
- expect(result).toBeDefined();
155
- expect(result).not.toContain("~/");
156
- expect(result).toContain("some/path");
157
- });
158
-
159
- it("returns absolute paths unchanged", () => {
160
- expect(resolveHomePath("/absolute/path")).toBe("/absolute/path");
161
- });
162
-
163
- it("returns relative paths unchanged", () => {
164
- expect(resolveHomePath("relative/path")).toBe("relative/path");
165
- });
166
- });
167
-
168
- describe("normalizeWarmupPath", () => {
169
- it("returns /user for empty string", () => {
170
- expect(normalizeWarmupPath("")).toBe("/user");
171
- });
172
-
173
- it("returns /user for whitespace-only string", () => {
174
- expect(normalizeWarmupPath(" ")).toBe("/user");
175
- });
176
-
177
- it("preserves leading slash", () => {
178
- expect(normalizeWarmupPath("/custom")).toBe("/custom");
179
- });
180
-
181
- it("adds leading slash when missing", () => {
182
- expect(normalizeWarmupPath("custom")).toBe("/custom");
183
- });
184
-
185
- it("trims whitespace", () => {
186
- expect(normalizeWarmupPath(" /user ")).toBe("/user");
187
- });
188
- });
189
-
190
- describe("resolveApiRoot", () => {
191
- it("extracts /api/v4 from standard URL", () => {
192
- const url = new URL("https://gitlab.example.com/api/v4/projects/1");
193
- expect(resolveApiRoot(url)).toBe("/api/v4");
194
- });
195
-
196
- it("extracts subpath /api/v4", () => {
197
- const url = new URL("https://example.com/gitlab/api/v4/projects");
198
- expect(resolveApiRoot(url)).toBe("/gitlab/api/v4");
199
- });
200
-
201
- it("returns undefined for non-API URLs", () => {
202
- const url = new URL("https://gitlab.example.com/group/project");
203
- expect(resolveApiRoot(url)).toBeUndefined();
204
- });
205
-
206
- it("matches when path ends with /api/v4", () => {
207
- const url = new URL("https://gitlab.example.com/api/v4/");
208
- expect(resolveApiRoot(url)).toBe("/api/v4");
209
- });
210
- });
211
-
212
- describe("parseOauthScopes", () => {
213
- it("parses space-separated scopes", () => {
214
- expect(parseOauthScopes("api read_user")).toEqual(["api", "read_user"]);
215
- });
216
-
217
- it("parses comma-separated scopes", () => {
218
- expect(parseOauthScopes("api,read_user,write_repository")).toEqual([
219
- "api",
220
- "read_user",
221
- "write_repository"
222
- ]);
223
- });
224
-
225
- it("handles mixed separators", () => {
226
- expect(parseOauthScopes("api, read_user write_repository")).toEqual([
227
- "api",
228
- "read_user",
229
- "write_repository"
230
- ]);
231
- });
232
-
233
- it("filters empty entries", () => {
234
- expect(parseOauthScopes("api,,read_user, ,write_repository")).toEqual([
235
- "api",
236
- "read_user",
237
- "write_repository"
238
- ]);
239
- });
240
-
241
- it("handles single scope", () => {
242
- expect(parseOauthScopes("api")).toEqual(["api"]);
243
- });
244
-
245
- it("handles empty string", () => {
246
- expect(parseOauthScopes("")).toEqual([]);
247
- });
248
-
249
- it("trims whitespace from scopes", () => {
250
- expect(parseOauthScopes(" api , read_user ")).toEqual(["api", "read_user"]);
251
- });
252
- });
@@ -1,123 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { stripNullsDeep } from "../src/lib/sanitize.js";
4
-
5
- describe("stripNullsDeep", () => {
6
- it("removes null values recursively from objects", () => {
7
- const input = {
8
- title: "demo",
9
- description: null,
10
- nested: {
11
- keep: 1,
12
- drop: null,
13
- arr: [1, null, 2, null, 3]
14
- }
15
- };
16
-
17
- expect(stripNullsDeep(input)).toEqual({
18
- title: "demo",
19
- nested: {
20
- keep: 1,
21
- arr: [1, 2, 3]
22
- }
23
- });
24
- });
25
-
26
- it("returns undefined for top-level null", () => {
27
- expect(stripNullsDeep(null)).toBeUndefined();
28
- });
29
-
30
- it("preserves primitive string values", () => {
31
- expect(stripNullsDeep("hello")).toBe("hello");
32
- });
33
-
34
- it("preserves primitive number values", () => {
35
- expect(stripNullsDeep(42)).toBe(42);
36
- expect(stripNullsDeep(0)).toBe(0);
37
- expect(stripNullsDeep(-1)).toBe(-1);
38
- });
39
-
40
- it("preserves boolean values", () => {
41
- expect(stripNullsDeep(true)).toBe(true);
42
- expect(stripNullsDeep(false)).toBe(false);
43
- });
44
-
45
- it("preserves undefined as-is", () => {
46
- expect(stripNullsDeep(undefined)).toBeUndefined();
47
- });
48
-
49
- it("handles empty object", () => {
50
- expect(stripNullsDeep({})).toEqual({});
51
- });
52
-
53
- it("handles empty array", () => {
54
- expect(stripNullsDeep([])).toEqual([]);
55
- });
56
-
57
- it("handles object with all null values", () => {
58
- expect(stripNullsDeep({ a: null, b: null, c: null })).toEqual({});
59
- });
60
-
61
- it("handles array with all null values", () => {
62
- expect(stripNullsDeep([null, null, null])).toEqual([]);
63
- });
64
-
65
- it("handles deeply nested null removal", () => {
66
- const input = {
67
- level1: {
68
- level2: {
69
- level3: {
70
- keep: "value",
71
- drop: null
72
- }
73
- }
74
- }
75
- };
76
-
77
- expect(stripNullsDeep(input)).toEqual({
78
- level1: {
79
- level2: {
80
- level3: {
81
- keep: "value"
82
- }
83
- }
84
- }
85
- });
86
- });
87
-
88
- it("handles mixed array contents", () => {
89
- const input = [1, "hello", null, true, null, { key: null, keep: "yes" }];
90
-
91
- expect(stripNullsDeep(input)).toEqual([1, "hello", true, { keep: "yes" }]);
92
- });
93
-
94
- it("handles nested arrays within objects", () => {
95
- const input = {
96
- items: [{ name: "a", value: null }, null, { name: "b", value: 1 }]
97
- };
98
-
99
- expect(stripNullsDeep(input)).toEqual({
100
- items: [{ name: "a" }, { name: "b", value: 1 }]
101
- });
102
- });
103
-
104
- it("preserves zero, empty string, and false values", () => {
105
- const input = {
106
- zero: 0,
107
- emptyStr: "",
108
- falsy: false,
109
- nullValue: null
110
- };
111
-
112
- expect(stripNullsDeep(input)).toEqual({
113
- zero: 0,
114
- emptyStr: "",
115
- falsy: false
116
- });
117
- });
118
-
119
- it("handles arrays nested within arrays", () => {
120
- const input = [[null, 1], [2, null], null];
121
- expect(stripNullsDeep(input)).toEqual([[1], [2]]);
122
- });
123
- });
@@ -1,49 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { getTotalSessions, hasReachedSessionCapacity } from "../src/lib/session-capacity.js";
4
-
5
- describe("session capacity helpers", () => {
6
- it("counts streamable, pending, and SSE sessions together", () => {
7
- expect(
8
- getTotalSessions({
9
- streamableSessions: 2,
10
- pendingSessions: 1,
11
- sseSessions: 3,
12
- maxSessions: 10
13
- })
14
- ).toBe(6);
15
- });
16
-
17
- it("treats capacity as reached when total equals max", () => {
18
- expect(
19
- hasReachedSessionCapacity({
20
- streamableSessions: 1,
21
- pendingSessions: 1,
22
- sseSessions: 1,
23
- maxSessions: 3
24
- })
25
- ).toBe(true);
26
- });
27
-
28
- it("treats capacity as reached when total exceeds max", () => {
29
- expect(
30
- hasReachedSessionCapacity({
31
- streamableSessions: 2,
32
- pendingSessions: 1,
33
- sseSessions: 2,
34
- maxSessions: 4
35
- })
36
- ).toBe(true);
37
- });
38
-
39
- it("does not reach capacity when total is below max", () => {
40
- expect(
41
- hasReachedSessionCapacity({
42
- streamableSessions: 1,
43
- pendingSessions: 0,
44
- sseSessions: 1,
45
- maxSessions: 3
46
- })
47
- ).toBe(false);
48
- });
49
- });
@@ -1,88 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { parseProjectUploadReference } from "../src/tools/gitlab.js";
4
-
5
- describe("parseProjectUploadReference", () => {
6
- it("parses absolute upload URL", () => {
7
- expect(
8
- parseProjectUploadReference("https://gitlab.example.com/group/repo/uploads/abc123/file.txt")
9
- ).toEqual({
10
- secret: "abc123",
11
- filename: "file.txt"
12
- });
13
- });
14
-
15
- it("parses relative upload path", () => {
16
- expect(parseProjectUploadReference("/uploads/abc123/file.txt")).toEqual({
17
- secret: "abc123",
18
- filename: "file.txt"
19
- });
20
- });
21
-
22
- it("parses relative upload path with encoded filename", () => {
23
- expect(parseProjectUploadReference("/uploads/abc123/%E6%B5%8B%E8%AF%95.txt")).toEqual({
24
- secret: "abc123",
25
- filename: "测试.txt"
26
- });
27
- });
28
-
29
- it("returns undefined for non-upload paths", () => {
30
- expect(parseProjectUploadReference("/api/v4/projects/1/issues")).toBeUndefined();
31
- });
32
-
33
- it("returns undefined for empty string", () => {
34
- expect(parseProjectUploadReference("")).toBeUndefined();
35
- });
36
-
37
- it("returns undefined for whitespace-only string", () => {
38
- expect(parseProjectUploadReference(" ")).toBeUndefined();
39
- });
40
-
41
- it("handles URL with query parameters", () => {
42
- const result = parseProjectUploadReference(
43
- "https://gitlab.example.com/group/repo/uploads/abc123/file.txt?inline=true"
44
- );
45
- expect(result).toEqual({
46
- secret: "abc123",
47
- filename: "file.txt"
48
- });
49
- });
50
-
51
- it("handles URL with fragment", () => {
52
- const result = parseProjectUploadReference(
53
- "https://gitlab.example.com/group/repo/uploads/abc123/file.txt#section"
54
- );
55
- expect(result).toEqual({
56
- secret: "abc123",
57
- filename: "file.txt"
58
- });
59
- });
60
-
61
- it("handles URL with nested group paths", () => {
62
- const result = parseProjectUploadReference(
63
- "https://gitlab.example.com/group/subgroup/repo/uploads/secret123/document.pdf"
64
- );
65
- expect(result).toEqual({
66
- secret: "secret123",
67
- filename: "document.pdf"
68
- });
69
- });
70
-
71
- it("returns undefined for invalid URL", () => {
72
- expect(parseProjectUploadReference("not a url at all")).toBeUndefined();
73
- });
74
-
75
- it("returns undefined for malformed percent-encoding", () => {
76
- expect(parseProjectUploadReference("/uploads/abc123/%E0%A4%A")).toBeUndefined();
77
- });
78
-
79
- it("handles upload path without leading slash", () => {
80
- // This depends on implementation, but should handle gracefully
81
- const result = parseProjectUploadReference("uploads/abc123/file.txt");
82
- // May or may not match depending on implementation
83
- if (result) {
84
- expect(result.secret).toBe("abc123");
85
- expect(result.filename).toBe("file.txt");
86
- }
87
- });
88
- });
@@ -1,11 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "declaration": true,
5
- "outDir": "dist",
6
- "rootDir": "src",
7
- "sourceMap": true,
8
- "types": ["node"]
9
- },
10
- "include": ["src"]
11
- }
package/tsconfig.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "lib": ["ES2022", "DOM"],
7
- "strict": true,
8
- "noUncheckedIndexedAccess": true,
9
- "noImplicitOverride": true,
10
- "useUnknownInCatchVariables": true,
11
- "skipLibCheck": true,
12
- "resolveJsonModule": true,
13
- "esModuleInterop": false,
14
- "forceConsistentCasingInFileNames": true,
15
- "isolatedModules": true,
16
- "verbatimModuleSyntax": true,
17
- "types": ["node", "vitest/globals"]
18
- },
19
- "include": ["src", "tests", "eslint.config.js", "vitest.config.ts"],
20
- "exclude": ["dist", "node_modules"]
21
- }
package/vitest.config.ts DELETED
@@ -1,12 +0,0 @@
1
- import { defineConfig } from "vitest/config";
2
-
3
- export default defineConfig({
4
- test: {
5
- environment: "node",
6
- include: ["tests/**/*.test.ts"],
7
- coverage: {
8
- reporter: ["text", "html"],
9
- include: ["src/**/*.ts"]
10
- }
11
- }
12
- });