@t09tanaka/test-gates 0.0.0-stage → 0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Takuto Tanaka
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,365 @@
1
- # Temporary Holding Version
1
+ # test-gates
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Test gates for the code that must not be wrong.
4
+
5
+ This package guards the handful of modules where a wrong decision costs money or trust: amounts and rounding, state transitions of payments and orders, authentication and role checks, expiry and remaining-quota rules.
6
+
7
+ - **100% coverage is only the floor.** It proves that a test exists and touches every branch. It does not prove that the test checks the decision.
8
+ - **Mutation testing is the real check.** If the code can be changed without a test failing, the decision is not guarded. Every surviving mutant must be killed by a test, or allowed one by one with a written reason.
9
+ - **Important decisions live in pure modules, and pure modules are registered.** No decorators, no DI, no database, no framework runtime. The list of registered modules is one file: `test-gates.json`.
10
+
11
+ `test-gates` is a CLI plus three small config helpers. It has no runtime dependencies. Jest, Vitest and Stryker are the ones already installed in your project.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install -D @t09tanaka/test-gates
17
+ ```
18
+
19
+ Requires Node.js 20 or later. For mutation testing add Stryker and the runner for your test framework:
20
+
21
+ ```bash
22
+ npm install -D @stryker-mutator/core @stryker-mutator/jest-runner # or @stryker-mutator/vitest-runner
23
+ ```
24
+
25
+ ## Setup
26
+
27
+ Everything below is per subproject (the directory that has a `package.json`: `backend/`, `frontend/`, `api/`, ...).
28
+
29
+ ### 1. `test-gates.json`
30
+
31
+ ```json
32
+ {
33
+ "$schema": "./node_modules/@t09tanaka/test-gates/schema/test-gates.schema.json",
34
+ "gates": [
35
+ {
36
+ "path": "src/common/utils/money.ts",
37
+ "decides": "Validation and rounding of amounts",
38
+ "impact": "Wrong invoice and refund amounts"
39
+ }
40
+ ],
41
+ "candidates": [
42
+ {
43
+ "path": "src/admin/guards/admin-role.guard.ts",
44
+ "decides": "Who is an administrator",
45
+ "blocker": "Has decorators. The decision has to be extracted into a pure module"
46
+ }
47
+ ]
48
+ }
49
+ ```
50
+
51
+ - `gates`: pure modules that hold an important decision. Each one needs a spec next to it (`money.spec.ts` or `money.test.ts`), 100% on statements, branches, functions and lines, and no surviving mutant outside its allow list.
52
+ - `candidates`: important logic that cannot be a gate yet, with what blocks it. Nothing is enforced for these except that the path exists.
53
+ - `settings`: optional project conventions, see [Settings](#settings).
54
+
55
+ This file is the only place where the list lives. The configs below are derived from it.
56
+
57
+ ### 2. Gate config: only the specs of the gates, only the gates measured
58
+
59
+ Jest (`jest.gates.config.js`):
60
+
61
+ ```js
62
+ const { createJestGatesConfig } = require('@t09tanaka/test-gates/jest');
63
+
64
+ module.exports = createJestGatesConfig({
65
+ rootDir: __dirname,
66
+ transform: { '^.+\\.ts$': ['ts-jest', { diagnostics: false }] },
67
+ moduleNameMapper: { '^@/(.*)$': '<rootDir>/src/$1' },
68
+ });
69
+ ```
70
+
71
+ Vitest (`vitest.gates.config.ts`):
72
+
73
+ ```ts
74
+ import { defineConfig } from 'vitest/config';
75
+ import { createVitestGatesConfig } from '@t09tanaka/test-gates/vitest';
76
+
77
+ export default defineConfig(
78
+ createVitestGatesConfig({
79
+ rootDir: import.meta.url,
80
+ resolve: { alias: { '@': new URL('./src', import.meta.url).pathname } },
81
+ })
82
+ );
83
+ ```
84
+
85
+ Do not use a database, a custom test environment or a global setup here. A spec that needs them does not verify a pure module; move the file to `candidates`.
86
+
87
+ ### 3. Stryker config (`stryker.gates.config.mjs`)
88
+
89
+ ```js
90
+ import { createStrykerGatesConfig } from '@t09tanaka/test-gates/stryker';
91
+
92
+ export default createStrykerGatesConfig({
93
+ rootDir: import.meta.url,
94
+ testRunner: 'jest',
95
+ jest: { projectType: 'custom', configFile: 'jest.gates.config.js' },
96
+ // testRunner: 'vitest', vitest: { configFile: 'vitest.gates.config.ts' },
97
+ concurrency: 2,
98
+ });
99
+ ```
100
+
101
+ Add `reports/` and `.stryker-tmp/` to `.gitignore`.
102
+
103
+ ### 4. Scripts
104
+
105
+ ```json
106
+ {
107
+ "scripts": {
108
+ "test:gates": "test-gates check && jest --config jest.gates.config.js",
109
+ "test:gates:mutation": "test-gates mutation",
110
+ "test:gates:selfcheck": "test-gates selfcheck"
111
+ }
112
+ }
113
+ ```
114
+
115
+ With Vitest, `test:gates` is `test-gates check && vitest run --config vitest.gates.config.ts`.
116
+
117
+ Arguments after `--` go to Stryker and nowhere else:
118
+
119
+ ```bash
120
+ npm run test:gates:mutation -- --force --concurrency 1
121
+ ```
122
+
123
+ ### 5. Where to run it
124
+
125
+ pre-push (husky), only for the subprojects the push touches:
126
+
127
+ ```sh
128
+ #!/bin/sh
129
+ default_branch=$(git symbolic-ref -q --short refs/remotes/origin/HEAD || echo origin/main)
130
+ changed=""
131
+ while read -r _local_ref local_sha _remote_ref remote_sha; do
132
+ case "$local_sha" in *[!0]*) ;; *) continue ;; esac # branch deletion
133
+ if git cat-file -e "$remote_sha^{commit}" 2>/dev/null; then
134
+ base="$remote_sha"
135
+ else
136
+ base=$(git merge-base "$local_sha" "$default_branch" 2>/dev/null || true)
137
+ fi
138
+ if [ -n "$base" ]; then
139
+ changed="$changed
140
+ $(git diff --name-only "$base" "$local_sha")"
141
+ else
142
+ changed="$changed
143
+ $(git ls-tree -r --name-only "$local_sha")"
144
+ fi
145
+ done
146
+
147
+ for project in backend frontend; do
148
+ printf '%s\n' "$changed" | grep -q "^$project/" || continue
149
+ (cd "$project" && npm run --silent test:gates && npm run --silent test:gates:mutation) || exit 1
150
+ done
151
+ ```
152
+
153
+ CI (GitHub Actions), one job per subproject, no database service needed:
154
+
155
+ ```yaml
156
+ test-gates:
157
+ runs-on: ubuntu-latest
158
+ defaults:
159
+ run:
160
+ working-directory: backend
161
+ steps:
162
+ - uses: actions/checkout@v4
163
+ - uses: actions/setup-node@v4
164
+ with:
165
+ node-version-file: backend/.node-version
166
+ cache: npm
167
+ cache-dependency-path: backend/package-lock.json
168
+ - run: npm ci
169
+ - run: npm run test:gates
170
+ - run: npm run test:gates:selfcheck
171
+ - run: npm run test:gates:mutation
172
+ ```
173
+
174
+ ## Commands
175
+
176
+ All commands take `--dir <subproject>` (default: the current directory).
177
+
178
+ | Exit code | Meaning |
179
+ | --------- | -------------------------------------------------------------------------- |
180
+ | 0 | Passed |
181
+ | 1 | Violations found, or a run that could not be judged (e.g. missing report) |
182
+ | 2 | Wrong usage, or `test-gates.json` is missing, broken or has an unknown key |
183
+
184
+ ### `test-gates check`
185
+
186
+ Static checks, before the gate run. Reports every violation as `file:line: reason`.
187
+
188
+ - Every gate exists, and so does its spec. **The check is case-sensitive** even on macOS and Windows, because CI on Linux is.
189
+ - No path is listed twice, or in both `gates` and `candidates`. Candidates exist.
190
+ - A gate has no decorator (`@Name(` at the start of a line) and no runtime import of a framework, database client or SDK. `import type` is ignored.
191
+ - A gate and its spec have no coverage ignore directive (`istanbul ignore`, `c8 ignore`, `v8 ignore`).
192
+ - A spec has no `.skip`, `.only`, `.todo`, `xit`, `xdescribe`, `fit`, `fdescribe`, `.skipIf`, `.runIf`, `.fails`.
193
+ - A spec does not `jest.mock` / `vi.mock` the module it is supposed to test.
194
+ - A gate has no `Stryker disable` / `Stryker restore` comment. Such a comment turns off a whole line for a whole mutator, which takes non-equivalent mutants out with it.
195
+ - `equivalentMutants` entries are well formed and each has a `reason`.
196
+ - The Stryker config does not use `excludedMutations`, `ignoreStatic` or `ignorers`.
197
+
198
+ ### `test-gates mutation [-- <stryker arguments>]`
199
+
200
+ Runs `stryker run <config>` from the project's own `node_modules`, then judges the JSON report (see `mutation-result`). Unknown arguments, and everything after `--`, are handed to Stryker. If Stryker itself fails, the exit code is 1 and nothing is judged. A report from an earlier run is deleted first, so a run that writes none cannot pass on old results.
201
+
202
+ ### `test-gates mutation-result [--report <path>]`
203
+
204
+ Judges an existing Stryker JSON report without running Stryker.
205
+
206
+ | Stryker status | Treated as |
207
+ | ----------------------------------- | ------------------------------------------------------------------- |
208
+ | `Killed`, `Timeout` | Detected |
209
+ | `Survived`, `NoCoverage` | Survivor. Must match exactly one allowance, otherwise the run fails |
210
+ | `RuntimeError`, `CompileError` | Not evaluable. Counted and shown, does not fail the run |
211
+ | `Ignored`, `Pending`, anything else | Failure. A mutant that was not evaluated proves nothing |
212
+
213
+ The run also fails when
214
+
215
+ - an allowance matches no surviving mutant of this run (stale: the mutant is now killed, or the code changed),
216
+ - an allowance matches several mutants and has no `occurrence`,
217
+ - two allowances point at the same mutant, or an allowance has no `reason`,
218
+ - a gate is missing from the report or has no mutants, or no mutant was evaluated at all,
219
+ - the report was made from a different version of a gate than the one on disk.
220
+
221
+ `Timeout` counts as detected, but its count is always printed on its own (`detected N (timeout M)`). On a loaded machine mutants time out that would otherwise survive, so a rising timeout count means the run may be hiding survivors: rerun with lower `--concurrency`.
222
+
223
+ On failure every unallowed survivor is listed as `file:line:column / mutator / original → replacement / occurrence`, followed by a JSON fragment that can be pasted into `equivalentMutants`. The last line gives the real numbers, never a percentage:
224
+
225
+ ```
226
+ test-gates mutation: OK (mutants 317 / detected 308 (timeout 0) / allowed equivalent 8 / not evaluable 1 / unallowed survivors 0)
227
+ ```
228
+
229
+ ### `test-gates selfcheck [--all | --first] [-- <gate command>]`
230
+
231
+ Negative control. For each gate, runs the gate with that gate's spec left out and requires the run to **fail on the coverage threshold of that very file**. A gate that stays green without its spec measures nothing; a run that fails for another reason (a config that does not load) proves nothing either.
232
+
233
+ The spec to leave out is passed in the environment variable `TEST_GATES_EXCLUDE_SPEC`. The config helpers read it. The gate command is `jest --config jest.gates.config.*` or `vitest run --config vitest.gates.config.*`, whichever config exists; override it with `settings.gateCommand` or after `--`. `--all` (default) checks every gate in turn, `--first` only the first.
234
+
235
+ ### `test-gates lcov --file <lcov.info>`
236
+
237
+ For stacks without mutation testing (Flutter / Dart): requires `LH == LF` for every gate in an lcov tracefile. A gate without a record, or with no instrumented line, fails. Paths are compared as written in the tracefile (`lib/models/coupon.dart`); absolute paths under the subproject are made relative.
238
+
239
+ ```bash
240
+ flutter test --coverage && npx test-gates lcov --file coverage/lcov.info
241
+ ```
242
+
243
+ ## Allowing an equivalent mutant
244
+
245
+ An equivalent mutant changes the code without changing anything observable: same return value, same exception, same side effects, for every input. Only those may be allowed, in `test-gates.json`, with the reason:
246
+
247
+ ```json
248
+ {
249
+ "path": "src/lib/auth/safe-compare.ts",
250
+ "decides": "Constant-time comparison of tokens",
251
+ "impact": "Token comparison leaks timing or accepts a wrong token",
252
+ "equivalentMutants": [
253
+ {
254
+ "mutator": "ConditionalExpression",
255
+ "original": "a.length !== b.length",
256
+ "replacement": "false",
257
+ "reason": "timingSafeEqual below throws on a length mismatch and the catch returns false, so the result is the same"
258
+ }
259
+ ]
260
+ }
261
+ ```
262
+
263
+ - The key is `mutator` + `original` + `replacement`. `original` is the source text at the location Stryker reports (taken from the report's copy of the source). Whitespace differences are ignored. **Line numbers are not part of the key**, so moving code does not break an allowance, and an allowance never covers a different mutant on the same line.
264
+ - When the same key occurs more than once in a file, `occurrence` (1-based, in source order) is required.
265
+ - An error message or a label is not equivalent just because no test reads it. If it matters, assert it (`toThrow(/…/)`).
266
+ - If in doubt, write the test.
267
+
268
+ Expected values in the tests come from the specification, not from the implementation's own constants: a test that loops over the constant it is checking cannot notice that the constant is wrong.
269
+
270
+ ## Settings
271
+
272
+ All optional. Patterns are regular expressions written as JSON strings; a rule is either a string or `{ "pattern", "flags", "reason" }`. An unknown key is an error (exit code 2), so a typo cannot silently fall back to a default.
273
+
274
+ | Setting | Default | Purpose |
275
+ | -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
276
+ | `gateExtensions` | `.ts .mts .cts .js .mjs .cjs` | Extensions a gate may have |
277
+ | `spec.suffixes` | `.spec<ext>` and `.test<ext>` of the gate | Replaces the gate's extension to form the spec path. Exactly one must exist. `[]` means no spec convention (spec checks are skipped) |
278
+ | `spec.rewrite` | none | `[{ "from", "to" }]` applied to the gate path first, for specs in another directory (`^app/` → `tests/`) |
279
+ | `impureImports.defaults` | `true` | Built-in list, see below |
280
+ | `impureImports.add` | none | More module patterns a gate must not import at runtime |
281
+ | `impureImports.allow` | none | Module patterns exempt from every rule (e.g. `^express$` where `express` is imported only for its types without `import type`) |
282
+ | `impureNamedImports` | `PrismaClient` from `@prisma/client` | `{ "defaults", "add": [{ "module", "names", "reason" }] }`: names banned from a module that is otherwise fine |
283
+ | `impurePaths` | `.vue` `.jsx` `.tsx`, `.d.ts` | `{ "defaults", "add" }`: gate paths that are rejected (`\\.service\\.ts$`, `^src/hooks/`) |
284
+ | `forbiddenSource` | `'use client'` / `'use server'` | `{ "defaults", "add" }`: lines that must not appear in a gate, tested one line at a time |
285
+ | `importAliases` | none | `[{ "prefix": "@/", "target": "src/" }]`, to recognise `jest.mock('@/x')` of the module under test |
286
+ | `gateCommand` | derived from `jest.gates.config.*` / `vitest.gates.config.*` | Command `selfcheck` runs, as an array (`["jest", "--config", "jest.gates.config.js", "--maxWorkers=2"]`) |
287
+ | `selfcheck.mode` | `all` | `all` or `first` |
288
+ | `selfcheck.failurePattern` | a threshold message on a line naming the gate | Regular expression the failing run's output must match; `{gate}` stands for the gate path |
289
+ | `stryker.configFile` | `stryker.gates.config.mjs` (`.js`, `.cjs`, `.json`) | Stryker config |
290
+ | `stryker.reportFile` | `reports/mutation/mutation.json` | Where the JSON report is written (by the helper) and read (by the judge) |
291
+ | `lcov.file` | none | Tracefile for `test-gates lcov` when `--file` is not given |
292
+ | `lcov.summaryExclude` | none | Paths left out of the reference total (`\\.g\\.dart$`) |
293
+
294
+ Built-in impure imports: `@nestjs/*`, `class-validator`, `class-transformer`, `typeorm`, `sequelize`, `mongoose`, `knex`, `pg`, `mysql`, `mysql2`, `ioredis`, `redis`, `@prisma/adapter-*`, `react`, `react-dom`, `next`, `server-only`, `client-only`, `vue`, `vue-router`, `vue-i18n`, `pinia`, `nuxt`, `#app`, `#imports`, `*.vue`, `express`, `express-jwt`, `fastify`, `koa`, `rxjs`, `passport`, `passport-*`, `axios`, `openapi-fetch`, `stripe`, `nodemailer`, `firebase-admin`, `@aws-sdk/*`, `@sentry/*`, `@slack/*`, `@stripe/*`, and the Node.js modules `fs`, `http`, `https`, `http2`, `net`, `tls`, `dgram`, `dns`, `child_process`, `worker_threads`.
295
+
296
+ `@prisma/client` itself is allowed: `Prisma.Decimal` and enums are values a pure module may use. Only `PrismaClient` is banned.
297
+
298
+ Project-specific layers go into `add`:
299
+
300
+ ```json
301
+ {
302
+ "settings": {
303
+ "spec": { "suffixes": [".spec.ts"] },
304
+ "importAliases": [{ "prefix": "@/", "target": "src/" }],
305
+ "impureImports": {
306
+ "add": [
307
+ {
308
+ "pattern": "(^|/)(repository|wrapper|handler)(/|$)",
309
+ "reason": "DB / external API layer"
310
+ },
311
+ { "pattern": "\\.(service|guard|controller|module)$", "reason": "class managed by DI" }
312
+ ]
313
+ },
314
+ "impurePaths": {
315
+ "add": [
316
+ {
317
+ "pattern": "\\.(service|guard|controller|module|dto)\\.ts$",
318
+ "reason": "NestJS class file"
319
+ }
320
+ ]
321
+ }
322
+ }
323
+ }
324
+ ```
325
+
326
+ ## Config helpers
327
+
328
+ ```ts
329
+ import { createJestGatesConfig } from '@t09tanaka/test-gates/jest';
330
+ import { createVitestGatesConfig } from '@t09tanaka/test-gates/vitest';
331
+ import { createStrykerGatesConfig } from '@t09tanaka/test-gates/stryker';
332
+ import { loadGates } from '@t09tanaka/test-gates';
333
+ ```
334
+
335
+ Each helper is available as ES module and CommonJS. `rootDir` is the directory of `test-gates.json`: `__dirname`, or `import.meta.url` of the config file. Every other option is passed through, except the keys that make the run a gate. Those are always set by the helper and cannot be overridden:
336
+
337
+ | Helper | Always set |
338
+ | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
339
+ | `createJestGatesConfig` | `testMatch` (specs of the gates), `collectCoverage`, `collectCoverageFrom` (the gates), `coverageProvider: 'babel'`, `coverageThreshold` (one absolute-path key per gate, four metrics at 100), `passWithNoTests` |
340
+ | `createVitestGatesConfig` | `root`, `test.include`, `test.passWithNoTests`, `test.coverage.enabled`, `test.coverage.include`, `test.coverage.thresholds: { perFile: true, 100 × 4 }` |
341
+ | `createStrykerGatesConfig` | `mutate` (the gates), `thresholds.break: null`, `json` among the `reporters`, `jsonReporter.fileName`. Throws on `ignoreStatic`, `ignorers`, `mutator.excludedMutations` |
342
+
343
+ Why these choices:
344
+
345
+ - Jest measures with istanbul (`babel`), because the v8 provider does not count the untaken side of an `if` without `else`.
346
+ - Jest fails on a `coverageThreshold` path key that matches no file, so a typo in `test-gates.json` cannot pass. Vitest does the opposite: a glob or path key in `thresholds` that matches nothing passes. The Vitest helper therefore narrows `coverage.include` to the gates and uses `perFile`.
347
+ - Stryker never fails on its own score (`break: null`). The verdict comes from `test-gates mutation`, which knows the allow list.
348
+
349
+ `loadGates(dir)` returns the validated manifest: `gates` (each with its resolved `spec`), `candidates` and the resolved `settings`. It throws when the manifest has any problem.
350
+
351
+ ## Limitations and things measured
352
+
353
+ - **A file with decorators cannot reach 100% branches under Jest.** The type metadata emitted for decorators contains inline conditionals (`typeof X === "undefined" ? Object : X`) that no test can take both ways. That is why decorated files are rejected as gates instead of being given a lower threshold.
354
+ - **Stryker does not mutate everything.** A constant property reference such as `Decimal.ROUND_UP` is left alone. Guard the rounding direction with an explicit test.
355
+ - **Stryker 10 does not start on Node.js 22.10** (`ERR_REQUIRE_ESM`). It runs on 22.14 and 22.22. On Node.js below 22.12 use Stryker 9.
356
+ - **Do not call the gate while the spec file is being collected** (in the body of `describe`, or at module level). The mutants reached that way become _static_ mutants, and with Stryker 10 and Vitest 3 a spec file that throws during collection was reported as `Survived`, not `Killed`. Call the gate inside `it` / `beforeEach`.
357
+ - **Jest treats an empty `testMatch` as its default pattern** and would run every test of the project. The helpers never produce an empty list; if you write the gate config by hand, do not either.
358
+ - **Imports are found by text, not by parsing.** The checks run without the project's dependencies installed, so an import inside a comment is reported too, and only the gate's own imports are looked at, not what those modules import in turn.
359
+ - **An ordinary `import { Request } from 'express'` used only as a type is reported.** Write `import type`, or exempt the module with `impureImports.allow`.
360
+ - **`test-gates lcov` checks lines only.** lcov from Flutter carries no branch data, and Dart has no established mutation testing tool; a Dart gate is guarded by coverage alone.
361
+ - **The checks do not judge what belongs in `gates`.** Whether every important decision has been extracted and registered is for people to review; `candidates` is where the known gaps are written down.
362
+
363
+ ## License
364
+
365
+ MIT
@@ -0,0 +1,18 @@
1
+ // src/shared.ts
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ var FULL_COVERAGE = { statements: 100, branches: 100, functions: 100, lines: 100 };
5
+ function resolveRootDir(rootDir) {
6
+ if (rootDir === void 0) {
7
+ return process.cwd();
8
+ }
9
+ if (rootDir.startsWith("file:")) {
10
+ return path.dirname(fileURLToPath(rootDir));
11
+ }
12
+ return path.resolve(rootDir);
13
+ }
14
+
15
+ export {
16
+ FULL_COVERAGE,
17
+ resolveRootDir
18
+ };