@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/package.json CHANGED
@@ -1,6 +1,134 @@
1
1
  {
2
2
  "name": "@t09tanaka/test-gates",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Test gates for the code that must not be wrong: per-file 100% coverage as the floor, mutation testing with a per-mutant allow list as the real check.",
5
+ "author": "t09tanaka",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/t09tanaka/test-gates.git"
10
+ },
11
+ "homepage": "https://github.com/t09tanaka/test-gates#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/t09tanaka/test-gates/issues"
14
+ },
15
+ "keywords": [
16
+ "test",
17
+ "coverage",
18
+ "mutation-testing",
19
+ "stryker",
20
+ "jest",
21
+ "vitest",
22
+ "lcov",
23
+ "quality-gate",
24
+ "cli"
25
+ ],
26
+ "type": "module",
27
+ "bin": {
28
+ "test-gates": "dist/cli.js"
29
+ },
30
+ "main": "./dist/index.cjs",
31
+ "module": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "exports": {
34
+ ".": {
35
+ "import": {
36
+ "types": "./dist/index.d.ts",
37
+ "default": "./dist/index.js"
38
+ },
39
+ "require": {
40
+ "types": "./dist/index.d.cts",
41
+ "default": "./dist/index.cjs"
42
+ }
43
+ },
44
+ "./jest": {
45
+ "import": {
46
+ "types": "./dist/jest.d.ts",
47
+ "default": "./dist/jest.js"
48
+ },
49
+ "require": {
50
+ "types": "./dist/jest.d.cts",
51
+ "default": "./dist/jest.cjs"
52
+ }
53
+ },
54
+ "./vitest": {
55
+ "import": {
56
+ "types": "./dist/vitest.d.ts",
57
+ "default": "./dist/vitest.js"
58
+ },
59
+ "require": {
60
+ "types": "./dist/vitest.d.cts",
61
+ "default": "./dist/vitest.cjs"
62
+ }
63
+ },
64
+ "./stryker": {
65
+ "import": {
66
+ "types": "./dist/stryker.d.ts",
67
+ "default": "./dist/stryker.js"
68
+ },
69
+ "require": {
70
+ "types": "./dist/stryker.d.cts",
71
+ "default": "./dist/stryker.cjs"
72
+ }
73
+ },
74
+ "./schema.json": "./schema/test-gates.schema.json",
75
+ "./package.json": "./package.json"
76
+ },
77
+ "files": [
78
+ "dist",
79
+ "schema"
80
+ ],
81
+ "engines": {
82
+ "node": ">=20"
83
+ },
84
+ "publishConfig": {
85
+ "access": "public"
86
+ },
87
+ "scripts": {
88
+ "build": "tsup",
89
+ "typecheck": "tsc --noEmit",
90
+ "lint": "eslint src test",
91
+ "lint:fix": "eslint src test --fix",
92
+ "format": "prettier --write .",
93
+ "format:check": "prettier --check .",
94
+ "pretest": "npm run build",
95
+ "test": "vitest run",
96
+ "test:watch": "vitest",
97
+ "test:coverage": "vitest run --coverage",
98
+ "test:gates": "node dist/cli.js check && vitest run --config vitest.gates.config.ts",
99
+ "test:gates:selfcheck": "node dist/cli.js selfcheck",
100
+ "test:gates:mutation": "node dist/cli.js mutation",
101
+ "prepublishOnly": "npm run lint && npm run format:check && npm run typecheck && npm test && npm run test:gates && npm run test:gates:selfcheck"
102
+ },
103
+ "peerDependencies": {
104
+ "@stryker-mutator/core": ">=9",
105
+ "jest": ">=29",
106
+ "vitest": ">=3"
107
+ },
108
+ "peerDependenciesMeta": {
109
+ "@stryker-mutator/core": {
110
+ "optional": true
111
+ },
112
+ "jest": {
113
+ "optional": true
114
+ },
115
+ "vitest": {
116
+ "optional": true
117
+ }
118
+ },
119
+ "devDependencies": {
120
+ "@eslint/js": "^9.39.5",
121
+ "@stryker-mutator/core": "^10.0.0",
122
+ "@stryker-mutator/vitest-runner": "^10.0.0",
123
+ "@types/node": "^22.20.5",
124
+ "@vitest/coverage-v8": "^3.2.7",
125
+ "eslint": "^9.39.5",
126
+ "eslint-config-prettier": "^10.1.8",
127
+ "eslint-plugin-prettier": "^5.5.6",
128
+ "prettier": "^3.9.9",
129
+ "tsup": "^8.5.1",
130
+ "typescript": "^5.9.3",
131
+ "typescript-eslint": "^8.71.1",
132
+ "vitest": "^3.2.7"
133
+ }
134
+ }
@@ -0,0 +1,272 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://github.com/t09tanaka/test-gates/blob/main/schema/test-gates.schema.json",
4
+ "title": "test-gates.json",
5
+ "description": "The single source of truth for the test gates of one subproject (@t09tanaka/test-gates).",
6
+ "type": "object",
7
+ "required": ["gates"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "$schema": { "type": "string" },
11
+ "gates": {
12
+ "description": "Pure modules that hold an important decision. Each must reach 100% on all four coverage metrics and leave no surviving mutant outside its allow list.",
13
+ "type": "array",
14
+ "minItems": 1,
15
+ "items": { "$ref": "#/definitions/gate" }
16
+ },
17
+ "candidates": {
18
+ "description": "Important logic that cannot be a gate yet. Nothing is enforced for these beyond their existence.",
19
+ "type": "array",
20
+ "items": { "$ref": "#/definitions/candidate" }
21
+ },
22
+ "settings": { "$ref": "#/definitions/settings" }
23
+ },
24
+ "definitions": {
25
+ "nonEmptyString": { "type": "string", "minLength": 1, "pattern": "\\S" },
26
+ "gate": {
27
+ "type": "object",
28
+ "required": ["path", "decides", "impact"],
29
+ "additionalProperties": true,
30
+ "properties": {
31
+ "path": {
32
+ "description": "Path of the module, relative to the subproject, with forward slashes.",
33
+ "$ref": "#/definitions/nonEmptyString"
34
+ },
35
+ "decides": {
36
+ "description": "What this module decides.",
37
+ "$ref": "#/definitions/nonEmptyString"
38
+ },
39
+ "impact": {
40
+ "description": "What goes wrong for users or the business when the decision is wrong.",
41
+ "$ref": "#/definitions/nonEmptyString"
42
+ },
43
+ "equivalentMutants": {
44
+ "description": "Surviving mutants that are allowed because they cannot be observed from outside. One entry allows exactly one mutant.",
45
+ "type": "array",
46
+ "items": { "$ref": "#/definitions/equivalentMutant" }
47
+ }
48
+ }
49
+ },
50
+ "equivalentMutant": {
51
+ "type": "object",
52
+ "required": ["mutator", "original", "replacement", "reason"],
53
+ "additionalProperties": false,
54
+ "properties": {
55
+ "mutator": {
56
+ "description": "Stryker mutator name, e.g. ConditionalExpression.",
57
+ "$ref": "#/definitions/nonEmptyString"
58
+ },
59
+ "original": {
60
+ "description": "The source text the mutant replaces. Whitespace differences are ignored.",
61
+ "$ref": "#/definitions/nonEmptyString"
62
+ },
63
+ "replacement": {
64
+ "description": "The text Stryker puts in its place. May be empty.",
65
+ "type": "string"
66
+ },
67
+ "reason": {
68
+ "description": "Why no input can make the mutant observable.",
69
+ "$ref": "#/definitions/nonEmptyString"
70
+ },
71
+ "occurrence": {
72
+ "description": "1-based index among the mutants of the file with the same mutator, original and replacement. Required when there is more than one.",
73
+ "type": "integer",
74
+ "minimum": 1
75
+ }
76
+ }
77
+ },
78
+ "candidate": {
79
+ "type": "object",
80
+ "required": ["path", "decides", "blocker"],
81
+ "additionalProperties": true,
82
+ "properties": {
83
+ "path": {
84
+ "description": "File or directory, relative to the subproject.",
85
+ "$ref": "#/definitions/nonEmptyString"
86
+ },
87
+ "decides": { "$ref": "#/definitions/nonEmptyString" },
88
+ "blocker": {
89
+ "description": "What has to happen before this can become a gate.",
90
+ "$ref": "#/definitions/nonEmptyString"
91
+ }
92
+ }
93
+ },
94
+ "patternRule": {
95
+ "description": "A regular expression, either as a string or with flags and a reason.",
96
+ "oneOf": [
97
+ { "$ref": "#/definitions/nonEmptyString" },
98
+ {
99
+ "type": "object",
100
+ "required": ["pattern"],
101
+ "additionalProperties": false,
102
+ "properties": {
103
+ "pattern": { "$ref": "#/definitions/nonEmptyString" },
104
+ "flags": { "type": "string" },
105
+ "reason": { "type": "string" }
106
+ }
107
+ }
108
+ ]
109
+ },
110
+ "ruleList": {
111
+ "type": "object",
112
+ "additionalProperties": false,
113
+ "properties": {
114
+ "defaults": {
115
+ "description": "Set to false to drop the built-in rules.",
116
+ "type": "boolean",
117
+ "default": true
118
+ },
119
+ "add": { "type": "array", "items": { "$ref": "#/definitions/patternRule" } }
120
+ }
121
+ },
122
+ "settings": {
123
+ "description": "Per-project conventions. Everything is optional.",
124
+ "type": "object",
125
+ "additionalProperties": false,
126
+ "properties": {
127
+ "gateExtensions": {
128
+ "description": "File extensions a gate may have.",
129
+ "type": "array",
130
+ "minItems": 1,
131
+ "items": { "type": "string", "pattern": "^\\." },
132
+ "default": [".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"]
133
+ },
134
+ "spec": {
135
+ "type": "object",
136
+ "additionalProperties": false,
137
+ "properties": {
138
+ "suffixes": {
139
+ "description": "Replaces the gate's extension to form the spec path. Default: \".spec<ext>\" and \".test<ext>\". An empty array means the project has no spec convention.",
140
+ "type": "array",
141
+ "items": { "$ref": "#/definitions/nonEmptyString" }
142
+ },
143
+ "rewrite": {
144
+ "description": "Regular expression replacements applied to the gate path before the suffix, for specs that live in another directory.",
145
+ "type": "array",
146
+ "items": {
147
+ "type": "object",
148
+ "required": ["from", "to"],
149
+ "additionalProperties": false,
150
+ "properties": {
151
+ "from": { "$ref": "#/definitions/nonEmptyString" },
152
+ "to": { "type": "string" }
153
+ }
154
+ }
155
+ }
156
+ }
157
+ },
158
+ "impureImports": {
159
+ "description": "Modules a gate must not import at runtime (regular expressions tested against the module specifier). `import type` is never checked.",
160
+ "type": "object",
161
+ "additionalProperties": false,
162
+ "properties": {
163
+ "defaults": { "type": "boolean", "default": true },
164
+ "add": { "type": "array", "items": { "$ref": "#/definitions/patternRule" } },
165
+ "allow": {
166
+ "description": "Module specifiers exempt from every rule (regular expressions).",
167
+ "type": "array",
168
+ "items": { "$ref": "#/definitions/nonEmptyString" }
169
+ }
170
+ }
171
+ },
172
+ "impureNamedImports": {
173
+ "description": "Names a gate must not import from a module that is otherwise fine. Default: PrismaClient from @prisma/client.",
174
+ "type": "object",
175
+ "additionalProperties": false,
176
+ "properties": {
177
+ "defaults": { "type": "boolean", "default": true },
178
+ "add": {
179
+ "type": "array",
180
+ "items": {
181
+ "type": "object",
182
+ "required": ["module", "names"],
183
+ "additionalProperties": false,
184
+ "properties": {
185
+ "module": { "$ref": "#/definitions/nonEmptyString" },
186
+ "names": {
187
+ "type": "array",
188
+ "minItems": 1,
189
+ "items": { "$ref": "#/definitions/nonEmptyString" }
190
+ },
191
+ "reason": { "type": "string" }
192
+ }
193
+ }
194
+ }
195
+ }
196
+ },
197
+ "impurePaths": {
198
+ "description": "Gate paths that are rejected (regular expressions). Default: components and declaration files.",
199
+ "$ref": "#/definitions/ruleList"
200
+ },
201
+ "forbiddenSource": {
202
+ "description": "Lines that must not appear in a gate (regular expressions, tested one line at a time). Default: 'use client' / 'use server'.",
203
+ "$ref": "#/definitions/ruleList"
204
+ },
205
+ "importAliases": {
206
+ "description": "Import aliases, used to recognise a spec that mocks the module under test.",
207
+ "type": "array",
208
+ "items": {
209
+ "type": "object",
210
+ "required": ["prefix", "target"],
211
+ "additionalProperties": false,
212
+ "properties": {
213
+ "prefix": { "$ref": "#/definitions/nonEmptyString" },
214
+ "target": { "type": "string" }
215
+ }
216
+ }
217
+ },
218
+ "gateCommand": {
219
+ "description": "The command `test-gates selfcheck` runs. Default: `jest --config jest.gates.config.*` or `vitest run --config vitest.gates.config.*`, whichever config exists.",
220
+ "type": "array",
221
+ "minItems": 1,
222
+ "items": { "$ref": "#/definitions/nonEmptyString" }
223
+ },
224
+ "selfcheck": {
225
+ "type": "object",
226
+ "additionalProperties": false,
227
+ "properties": {
228
+ "mode": {
229
+ "description": "Run the negative control for every gate, or only for the first.",
230
+ "enum": ["all", "first"],
231
+ "default": "all"
232
+ },
233
+ "failurePattern": {
234
+ "description": "Regular expression the output of the failing gate run must match. `{gate}` stands for the gate path. Default: a coverage threshold message on a line that names the gate.",
235
+ "$ref": "#/definitions/nonEmptyString"
236
+ }
237
+ }
238
+ },
239
+ "stryker": {
240
+ "type": "object",
241
+ "additionalProperties": false,
242
+ "properties": {
243
+ "configFile": {
244
+ "description": "Default: stryker.gates.config.mjs (.js / .cjs / .json).",
245
+ "$ref": "#/definitions/nonEmptyString"
246
+ },
247
+ "reportFile": {
248
+ "description": "Where the Stryker JSON report is written and read.",
249
+ "$ref": "#/definitions/nonEmptyString",
250
+ "default": "reports/mutation/mutation.json"
251
+ }
252
+ }
253
+ },
254
+ "lcov": {
255
+ "type": "object",
256
+ "additionalProperties": false,
257
+ "properties": {
258
+ "file": {
259
+ "description": "Tracefile for `test-gates lcov` when --file is not given.",
260
+ "$ref": "#/definitions/nonEmptyString"
261
+ },
262
+ "summaryExclude": {
263
+ "description": "Paths left out of the reference total (regular expressions).",
264
+ "type": "array",
265
+ "items": { "$ref": "#/definitions/nonEmptyString" }
266
+ }
267
+ }
268
+ }
269
+ }
270
+ }
271
+ }
272
+ }