@avi2dg/checks 0.36.0 → 0.38.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/CHANGELOG.md +16 -0
- package/README.md +33 -20
- package/dependency-cruiser.config.js +92 -60
- package/dist/presets/dependency-cruiser.d.ts +15 -0
- package/dist/presets/dependency-cruiser.js +126 -0
- package/dist/presets/knip.d.ts +6 -0
- package/dist/presets/knip.js +15 -0
- package/dist/presets/oxlint.d.ts +33 -0
- package/dist/presets/oxlint.js +145 -0
- package/docs/configs/dependency-rules.md +28 -31
- package/docs/configs/effect-rules.md +47 -23
- package/docs/configs/native-settings.md +65 -8
- package/docs/configs/typescript-rules.md +7 -7
- package/docs/design.md +25 -8
- package/docs/gates/checks-effect-scope.md +63 -0
- package/docs/gates/checks-exports.md +5 -5
- package/docs/gates/checks-imports.md +64 -0
- package/docs/gates/checks-lint.md +1 -1
- package/docs/gates/checks-mutation-baseline.md +99 -0
- package/docs/gates/checks-mutation-compare.md +5 -2
- package/docs/gates/checks-mutation.md +2 -2
- package/docs/gates/checks-unused.md +4 -4
- package/oxlintrc.json +49 -10
- package/package.json +29 -7
- package/src/complexity/exports.ts +1 -1
- package/src/complexity/knip.ts +1 -1
- package/src/core/gates.ts +4 -0
- package/src/dependencies/imports.ts +94 -0
- package/src/dependencies/kit-defaults.ts +3 -0
- package/src/quality/effect-scope.ts +116 -0
- package/src/quality/jsonc-patch.ts +209 -0
- package/src/quality/presets/dependency-cruiser.ts +148 -0
- package/src/quality/presets/oxlint.ts +165 -0
- package/src/testing/mutation-baseline.ts +211 -0
- package/src/quality/presets/effect.oxlint.json +0 -11
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Every release of `@avi2dg/checks`, newest first, written by the release from its conventional commits.
|
|
4
4
|
|
|
5
|
+
## 0.38.0
|
|
6
|
+
|
|
7
|
+
Released 2026-10-10.
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **testing:** add checks-mutation-baseline to restore the baseline from a runner cache [#140](https://github.com/avi2d/checks/pull/140)
|
|
12
|
+
|
|
13
|
+
## 0.37.0
|
|
14
|
+
|
|
15
|
+
Released 2026-10-09.
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
- ship typed defineConfig builders for oxlint, knip and dependency-cruiser [#138](https://github.com/avi2d/checks/pull/138)
|
|
20
|
+
|
|
5
21
|
## 0.36.0
|
|
6
22
|
|
|
7
23
|
Released 2026-10-08.
|
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# checks
|
|
2
2
|
|
|
3
3
|
`@avi2dg/checks` is the kit of deterministic checks a TypeScript repository installs to hold its code, tests, commits, CI wiring and docs to one shared standard.
|
|
4
|
-
It ships the lint gates `checks-lint` runs over each pull request, the test runners, and
|
|
4
|
+
It ships the lint gates `checks-lint` runs over each pull request, the test runners, and a `defineConfig` for oxlint, Knip and dependency-cruiser.
|
|
5
|
+
It also ships the configs a repository extends for tsc, commitlint, bun and Stryker.
|
|
5
6
|
Each repository owns its workflows and native tool configs, as [Native settings](docs/configs/native-settings.md) maps.
|
|
6
7
|
|
|
7
8
|
## Before you begin
|
|
@@ -42,13 +43,12 @@ To consume the kit from a repository:
|
|
|
42
43
|
|
|
43
44
|
<!-- end generated install -->
|
|
44
45
|
|
|
45
|
-
1.
|
|
46
|
+
1. Write `oxlint.config.ts` with the kit's builder, saying whether the sources are Effect programs:
|
|
46
47
|
|
|
47
|
-
```
|
|
48
|
-
{
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
48
|
+
```ts
|
|
49
|
+
import { defineConfig } from "@avi2dg/checks/oxlint";
|
|
50
|
+
|
|
51
|
+
export default defineConfig({ effect: true });
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
1. Keep oxlint out of `node_modules/` through `.gitignore`:
|
|
@@ -71,19 +71,27 @@ To consume the kit from a repository:
|
|
|
71
71
|
cp node_modules/@avi2dg/checks/bunfig.toml bunfig.toml
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
1. Name the
|
|
74
|
+
1. Name the files nothing imports in `knip.config.ts`:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { defineConfig } from "@avi2dg/checks/knip";
|
|
78
|
+
|
|
79
|
+
export default defineConfig({ entry: ["src/index.ts"] });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
1. Write `dependency-cruiser.config.ts` only when the kit's import rules need an entry point or a boundary of the repository's own:
|
|
75
83
|
|
|
76
84
|
```ts
|
|
77
|
-
import
|
|
85
|
+
import { defineConfig } from "@avi2dg/checks/dependency-cruiser";
|
|
78
86
|
|
|
79
|
-
export default {
|
|
87
|
+
export default defineConfig({ orphans: ["^src/bin[.]ts$"] });
|
|
80
88
|
```
|
|
81
89
|
|
|
82
90
|
1. Add scripts to `package.json`, replacing the build entry with the repository's own build command:
|
|
83
91
|
|
|
84
92
|
```json
|
|
85
|
-
"build": "bun build src/index.ts --outdir dist --target node",
|
|
86
|
-
"lint": "oxlint
|
|
93
|
+
"build": "bun build src/index.ts --outdir dist --target node && checks-effect-scope",
|
|
94
|
+
"lint": "oxlint && checks-lint",
|
|
87
95
|
"typecheck": "tsc --noEmit && effect-tsgo diagnostics --project tsconfig.json --format text --strict",
|
|
88
96
|
"test": "checks-test"
|
|
89
97
|
```
|
|
@@ -111,6 +119,7 @@ To consume the kit from a repository:
|
|
|
111
119
|
|
|
112
120
|
Add a pull request title lint step in another workflow using `./node_modules/.bin/commitlint`.
|
|
113
121
|
A private repository sets `runs-on: ${{ vars.CI_RUNS_ON || 'ubuntu-latest' }}` on each job, as [checks-ci-wiring](docs/gates/checks-ci-wiring.md#runners) requires.
|
|
122
|
+
`checks-effect-scope` writes the Effect paths of `oxlint.config.ts` and the kit's severities into `tsconfig.json`, as [The Effect rules](docs/configs/effect-rules.md#language-service) says.
|
|
114
123
|
`bun run lint` then ends with `checks-lint: <count> gate(s) pass`.
|
|
115
124
|
|
|
116
125
|
## What runs
|
|
@@ -129,6 +138,7 @@ The table groups the gates by vector, the part of a repository each one judges.
|
|
|
129
138
|
| complexity | [`checks-exports`](docs/gates/checks-exports.md) | the range | a repository tracking `*.ts` or `*.tsx` |
|
|
130
139
|
| quality | [`checks-lint-coverage`](docs/gates/checks-lint-coverage.md) | the working tree | a repository tracking `*.ts` or `*.tsx` or `*.astro` |
|
|
131
140
|
| quality | [`checks-comment-gate`](docs/gates/checks-comment-gate.md) | the range | every repository |
|
|
141
|
+
| quality | [`checks-effect-scope`](docs/gates/checks-effect-scope.md) | the working tree | a repository tracking `oxlint.config.ts` |
|
|
132
142
|
| quality | [`checks-frontend-syntax`](docs/gates/checks-frontend-syntax.md) | the working tree | a repository tracking `frontend-syntax.json` |
|
|
133
143
|
| testing | [`checks-test-layout`](docs/gates/checks-test-layout.md) | the working tree | a repository tracking `*.ts` or `*.tsx` |
|
|
134
144
|
| testing | [`checks-quarantine-clock`](docs/gates/checks-quarantine-clock.md) | the range | every repository |
|
|
@@ -136,6 +146,7 @@ The table groups the gates by vector, the part of a repository each one judges.
|
|
|
136
146
|
| delivery | [`checks-commit-identity`](docs/gates/checks-commit-identity.md) | the range | every repository |
|
|
137
147
|
| delivery | [`checks-ci-wiring`](docs/gates/checks-ci-wiring.md) | the working tree | every repository |
|
|
138
148
|
| delivery | [`checks-secrets`](docs/gates/checks-secrets.md) | the range | every repository |
|
|
149
|
+
| dependencies | [`checks-imports`](docs/gates/checks-imports.md) | the working tree | a repository tracking `*.ts` or `*.tsx` |
|
|
139
150
|
| dependencies | [`checks-advisories`](docs/gates/checks-advisories.md) | the range | a repository tracking `bun.lock` |
|
|
140
151
|
|
|
141
152
|
<!-- end generated gates -->
|
|
@@ -145,7 +156,8 @@ These bins run on their own:
|
|
|
145
156
|
- [`checks-test`](docs/gates/checks-test.md) runs the suite as `scripts.test` and refuses a skip without a reason at its test site.
|
|
146
157
|
- [`checks-flake`](docs/gates/checks-flake.md) runs the suite on a schedule and records the seeds a flaky test fails with.
|
|
147
158
|
- [`checks-mutation`](docs/gates/checks-mutation.md) runs Stryker for scoped checks and refuses a full run outside CI.
|
|
148
|
-
- [`checks-mutation-compare`](docs/gates/checks-mutation-compare.md)
|
|
159
|
+
- [`checks-mutation-compare`](docs/gates/checks-mutation-compare.md) compares the mutants of two reports and lists each one that regresses.
|
|
160
|
+
- [`checks-mutation-baseline`](docs/gates/checks-mutation-baseline.md) restores the newest mutation baseline from `main`, through a cache on a self-hosted runner.
|
|
149
161
|
- [`checks-subsumed-tests`](docs/gates/checks-subsumed-tests.md) lists each test another test subsumes in a mutation run.
|
|
150
162
|
- [`checks-changelog`](docs/gates/checks-changelog.md) writes the pending release into `CHANGELOG.md` from the conventional commits since the last release.
|
|
151
163
|
- [`checks-release-notes`](docs/gates/checks-release-notes.md) writes one `CHANGELOG.md` section to a file for a GitHub release.
|
|
@@ -156,7 +168,7 @@ These bins run on their own:
|
|
|
156
168
|
- [`checks-browser`](docs/gates/checks-browser.md) opens a product's built pages in Chrome and fails on the layout, keyboard, motion, accessibility, nesting and asset checks its `browser-checks.json` declares.
|
|
157
169
|
|
|
158
170
|
`checks-lint` has [its own page](docs/gates/checks-lint.md), which says which range it resolves.
|
|
159
|
-
|
|
171
|
+
oxlint and commitlint run through their own tools, as the pages under Related topics say.
|
|
160
172
|
|
|
161
173
|
## Upgrade
|
|
162
174
|
|
|
@@ -164,7 +176,8 @@ To move a repository to a newer release of the kit:
|
|
|
164
176
|
|
|
165
177
|
1. Run the install line again, which moves the kit to its newest release and the peers to the versions it pins.
|
|
166
178
|
A repository that opted in to `checks-browser` or `checks-frontend-syntax` also reruns the `bun add` line on its page.
|
|
167
|
-
1.
|
|
179
|
+
1. Run `bun run build`, whose `checks-effect-scope` rewrites the Effect override and the kit's severities in `tsconfig.json` when a release changes them.
|
|
180
|
+
A severity key the release drops stays in `tsconfig.json` until the repository removes it.
|
|
168
181
|
1. Copy `node_modules/@avi2dg/checks/bunfig.toml` over `bunfig.toml` again, since `checks-test-layout` compares the copy with the installed preset.
|
|
169
182
|
1. Run `bun run lint`, `bun run typecheck` and `bun run test`.
|
|
170
183
|
|
|
@@ -183,11 +196,11 @@ Every path is relative to the installed package, `node_modules/@avi2dg/checks/`.
|
|
|
183
196
|
| `docs/` | a reference page per bin and per shared config, and why the kit is shaped this way |
|
|
184
197
|
| `bunfig.toml` | the bunfig preset a repository copies |
|
|
185
198
|
| `commitlint.config.js` | the shared commitlint config |
|
|
186
|
-
| `dependency-cruiser.config.js` | the
|
|
187
|
-
| `knip-base.json` | the Knip
|
|
188
|
-
| `src/` | every bin, which a package script calls by its `checks-` name, the modules the bins import, and the Effect
|
|
189
|
-
| `dist/` | the compiled oxlint plugins and the doc templates, one template per kind of doc file |
|
|
190
|
-
| `oxlintrc.json` | the oxlint base config `.oxlintrc.json` extends |
|
|
199
|
+
| `dependency-cruiser.config.js` | the kit's dependency-cruiser rules as a base a `.dependency-cruiser.cjs` extends by path, which the build writes |
|
|
200
|
+
| `knip-base.json` | the Knip `include` setting, for a configuration that spreads it |
|
|
201
|
+
| `src/` | every bin, which a package script calls by its `checks-` name, the modules the bins import, and the Effect language service severities under `src/quality/presets/` |
|
|
202
|
+
| `dist/` | the compiled oxlint plugins, the config builders `@avi2dg/checks/oxlint`, `@avi2dg/checks/knip` and `@avi2dg/checks/dependency-cruiser` resolve to, and the doc templates, one template per kind of doc file |
|
|
203
|
+
| `oxlintrc.json` | the oxlint `base` as a config a `.oxlintrc.json` extends by path, which the build writes |
|
|
191
204
|
| `stryker.preset.js` | the Stryker mutation-testing preset, which refuses a full run outside CI |
|
|
192
205
|
| `tsconfig.effect.json` | the tsconfig fragment with the shared compiler options and the Effect language-service block |
|
|
193
206
|
| `ts-reset.d.ts` | the two ts-reset rules `tsconfig.effect.json` lists in `files` |
|
|
@@ -1,83 +1,115 @@
|
|
|
1
1
|
export default {
|
|
2
|
-
forbidden: [
|
|
2
|
+
"forbidden": [
|
|
3
3
|
{
|
|
4
|
-
name: "no-circular",
|
|
5
|
-
severity: "error",
|
|
6
|
-
comment: "A circular relationship never has a single entry point, so invert one side.",
|
|
7
|
-
from: {},
|
|
8
|
-
to: {
|
|
4
|
+
"name": "no-circular",
|
|
5
|
+
"severity": "error",
|
|
6
|
+
"comment": "A circular relationship never has a single entry point, so invert one side.",
|
|
7
|
+
"from": {},
|
|
8
|
+
"to": {
|
|
9
|
+
"circular": true
|
|
10
|
+
}
|
|
9
11
|
},
|
|
10
12
|
{
|
|
11
|
-
name: "no-orphans",
|
|
12
|
-
severity: "error",
|
|
13
|
-
comment:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
pathNot: [
|
|
13
|
+
"name": "no-orphans",
|
|
14
|
+
"severity": "error",
|
|
15
|
+
"comment": "Nothing reaches this module. Use it, remove it, or exempt the entry point by name in your own config.",
|
|
16
|
+
"from": {
|
|
17
|
+
"orphan": true,
|
|
18
|
+
"pathNot": [
|
|
18
19
|
"(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$",
|
|
19
20
|
"[.]d[.]ts$",
|
|
20
21
|
"(^|/)tsconfig[.]json$",
|
|
21
22
|
"(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$",
|
|
22
23
|
"(^|/)[^/]*[.]config[.](?:js|cjs|mjs|ts|cts|mts)$",
|
|
23
|
-
"[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$"
|
|
24
|
-
]
|
|
24
|
+
"[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$"
|
|
25
|
+
]
|
|
25
26
|
},
|
|
26
|
-
to: {}
|
|
27
|
+
"to": {}
|
|
27
28
|
},
|
|
28
29
|
{
|
|
29
|
-
name: "not-to-dev-dep",
|
|
30
|
-
severity: "error",
|
|
31
|
-
comment:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
pathNot: [
|
|
30
|
+
"name": "not-to-dev-dep",
|
|
31
|
+
"severity": "error",
|
|
32
|
+
"comment": "Shipped source cannot rely on a package that is absent in production. Move it to dependencies or peerDependencies, or keep the import in a test or config file.",
|
|
33
|
+
"from": {
|
|
34
|
+
"pathNot": [
|
|
35
35
|
"[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$",
|
|
36
|
-
"(^|/)[^/]*[.]config[.](?:js|cjs|mjs|ts|cts|mts)$"
|
|
37
|
-
]
|
|
38
|
-
},
|
|
39
|
-
to: {
|
|
40
|
-
dependencyTypes: ["npm-dev"],
|
|
41
|
-
dependencyTypesNot: ["type-only", "npm-peer"],
|
|
36
|
+
"(^|/)[^/]*[.]config[.](?:js|cjs|mjs|ts|cts|mts)$"
|
|
37
|
+
]
|
|
42
38
|
},
|
|
39
|
+
"to": {
|
|
40
|
+
"dependencyTypes": [
|
|
41
|
+
"npm-dev"
|
|
42
|
+
],
|
|
43
|
+
"dependencyTypesNot": [
|
|
44
|
+
"type-only",
|
|
45
|
+
"npm-peer"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
43
48
|
},
|
|
44
49
|
{
|
|
45
|
-
name: "no-non-package-json",
|
|
46
|
-
severity: "error",
|
|
47
|
-
comment:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
"name": "no-non-package-json",
|
|
51
|
+
"severity": "error",
|
|
52
|
+
"comment": "The import resolves to an installed package the nearest package.json does not declare, so it holds only while something else keeps it hoisted. Declare it in dependencies, devDependencies or peerDependencies.",
|
|
53
|
+
"from": {},
|
|
54
|
+
"to": {
|
|
55
|
+
"dependencyTypes": [
|
|
56
|
+
"npm-no-pkg",
|
|
57
|
+
"npm-unknown"
|
|
58
|
+
],
|
|
59
|
+
"dependencyTypesNot": [
|
|
60
|
+
"npm",
|
|
61
|
+
"npm-dev",
|
|
62
|
+
"npm-optional",
|
|
63
|
+
"npm-peer"
|
|
64
|
+
]
|
|
65
|
+
}
|
|
52
66
|
},
|
|
53
67
|
{
|
|
54
|
-
name: "not-to-unresolvable",
|
|
55
|
-
severity: "error",
|
|
56
|
-
comment: "Nothing installed answers to this specifier. Install the package or fix the path.",
|
|
57
|
-
from: {},
|
|
58
|
-
to: {
|
|
68
|
+
"name": "not-to-unresolvable",
|
|
69
|
+
"severity": "error",
|
|
70
|
+
"comment": "Nothing installed answers to this specifier. Install the package or fix the path.",
|
|
71
|
+
"from": {},
|
|
72
|
+
"to": {
|
|
73
|
+
"couldNotResolve": true
|
|
74
|
+
}
|
|
59
75
|
},
|
|
60
76
|
{
|
|
61
|
-
name: "no-deep-imports",
|
|
62
|
-
severity: "error",
|
|
63
|
-
comment:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
},
|
|
77
|
+
"name": "no-deep-imports",
|
|
78
|
+
"severity": "error",
|
|
79
|
+
"comment": "The specifier reaches past the package name into a subpath its exports map does not publish. Depend on a published entry instead.",
|
|
80
|
+
"from": {},
|
|
81
|
+
"to": {
|
|
82
|
+
"couldNotResolve": true,
|
|
83
|
+
"path": "^(@[^/]+/[^/]+|[^@./#][^/]*)/"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
71
86
|
],
|
|
72
|
-
options: {
|
|
73
|
-
parser: "swc",
|
|
74
|
-
builtInModules: {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
"options": {
|
|
88
|
+
"parser": "swc",
|
|
89
|
+
"builtInModules": {
|
|
90
|
+
"add": [
|
|
91
|
+
"bun"
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
"doNotFollow": {
|
|
95
|
+
"path": [
|
|
96
|
+
"node_modules"
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
"exclude": {
|
|
100
|
+
"path": "^repos/"
|
|
81
101
|
},
|
|
82
|
-
|
|
102
|
+
"enhancedResolveOptions": {
|
|
103
|
+
"exportsFields": [
|
|
104
|
+
"exports"
|
|
105
|
+
],
|
|
106
|
+
"conditionNames": [
|
|
107
|
+
"types",
|
|
108
|
+
"import",
|
|
109
|
+
"require",
|
|
110
|
+
"node",
|
|
111
|
+
"default"
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
83
115
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IConfiguration, ICruiseOptions } from "dependency-cruiser";
|
|
2
|
+
type CruiseOptions = Omit<ICruiseOptions, "builtInModules"> & {
|
|
3
|
+
readonly builtInModules?: Partial<NonNullable<ICruiseOptions["builtInModules"]>>;
|
|
4
|
+
};
|
|
5
|
+
export type CruiseConfig = Omit<IConfiguration, "extends" | "options"> & {
|
|
6
|
+
readonly options?: CruiseOptions;
|
|
7
|
+
};
|
|
8
|
+
export type ChecksCruiseConfig = CruiseConfig & {
|
|
9
|
+
readonly devOnly?: readonly string[];
|
|
10
|
+
readonly orphans?: readonly string[];
|
|
11
|
+
};
|
|
12
|
+
export declare const DEV_ONLY: readonly ["^tests/"];
|
|
13
|
+
export declare const base: CruiseConfig;
|
|
14
|
+
export declare function defineConfig({ devOnly, orphans, forbidden, options, ...rest }?: ChecksCruiseConfig): CruiseConfig;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// src/quality/presets/dependency-cruiser.ts
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
var DEV_ONLY = ["^tests/"];
|
|
4
|
+
var TEST_FILES = "[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$";
|
|
5
|
+
var MANIFEST = "package.json";
|
|
6
|
+
var CONFIG_FILES = "(^|/)[^/]*[.]config[.](?:js|cjs|mjs|ts|cts|mts)$";
|
|
7
|
+
var BASE = {
|
|
8
|
+
forbidden: [
|
|
9
|
+
{
|
|
10
|
+
name: "no-circular",
|
|
11
|
+
severity: "error",
|
|
12
|
+
comment: "A circular relationship never has a single entry point, so invert one side.",
|
|
13
|
+
from: {},
|
|
14
|
+
to: { circular: true }
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "no-orphans",
|
|
18
|
+
severity: "error",
|
|
19
|
+
comment: "Nothing reaches this module. Use it, remove it, or exempt the entry point by name in your own config.",
|
|
20
|
+
from: {
|
|
21
|
+
orphan: true,
|
|
22
|
+
pathNot: [
|
|
23
|
+
"(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$",
|
|
24
|
+
"[.]d[.]ts$",
|
|
25
|
+
"(^|/)tsconfig[.]json$",
|
|
26
|
+
"(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$",
|
|
27
|
+
CONFIG_FILES,
|
|
28
|
+
TEST_FILES
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
to: {}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "not-to-dev-dep",
|
|
35
|
+
severity: "error",
|
|
36
|
+
comment: "Shipped source cannot rely on a package that is absent in production. Move it to dependencies or peerDependencies, or keep the import in a test or config file.",
|
|
37
|
+
from: { pathNot: [TEST_FILES, CONFIG_FILES] },
|
|
38
|
+
to: { dependencyTypes: ["npm-dev"], dependencyTypesNot: ["type-only", "npm-peer"] }
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "no-non-package-json",
|
|
42
|
+
severity: "error",
|
|
43
|
+
comment: "The import resolves to an installed package the nearest package.json does not declare, so it holds only while something else keeps it hoisted. Declare it in dependencies, devDependencies or peerDependencies.",
|
|
44
|
+
from: {},
|
|
45
|
+
to: { dependencyTypes: ["npm-no-pkg", "npm-unknown"], dependencyTypesNot: ["npm", "npm-dev", "npm-optional", "npm-peer"] }
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "not-to-unresolvable",
|
|
49
|
+
severity: "error",
|
|
50
|
+
comment: "Nothing installed answers to this specifier. Install the package or fix the path.",
|
|
51
|
+
from: {},
|
|
52
|
+
to: { couldNotResolve: true }
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "no-deep-imports",
|
|
56
|
+
severity: "error",
|
|
57
|
+
comment: "The specifier reaches past the package name into a subpath its exports map does not publish. Depend on a published entry instead.",
|
|
58
|
+
from: {},
|
|
59
|
+
to: { couldNotResolve: true, path: "^(@[^/]+/[^/]+|[^@./#][^/]*)/" }
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
options: {
|
|
63
|
+
parser: "swc",
|
|
64
|
+
builtInModules: { add: ["bun"] },
|
|
65
|
+
doNotFollow: { path: ["node_modules"] },
|
|
66
|
+
exclude: { path: "^repos/" },
|
|
67
|
+
enhancedResolveOptions: {
|
|
68
|
+
exportsFields: ["exports"],
|
|
69
|
+
conditionNames: ["types", "import", "require", "node", "default"]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var base = BASE;
|
|
74
|
+
function asList(value) {
|
|
75
|
+
if (value === undefined)
|
|
76
|
+
return [];
|
|
77
|
+
return typeof value === "string" ? [value] : value;
|
|
78
|
+
}
|
|
79
|
+
function widened(rule, extra) {
|
|
80
|
+
if (extra.length === 0 || !("pathNot" in rule.from))
|
|
81
|
+
return rule;
|
|
82
|
+
return { ...rule, from: { ...rule.from, pathNot: [...asList(rule.from.pathNot), ...extra] } };
|
|
83
|
+
}
|
|
84
|
+
function kitRule(rule, devOnly, orphans) {
|
|
85
|
+
if (rule.name === "not-to-dev-dep")
|
|
86
|
+
return widened(rule, devOnly);
|
|
87
|
+
if (rule.name === "no-orphans")
|
|
88
|
+
return widened(rule, orphans);
|
|
89
|
+
return rule;
|
|
90
|
+
}
|
|
91
|
+
function excludeObject(exclude) {
|
|
92
|
+
if (exclude === undefined || typeof exclude === "string" || Array.isArray(exclude))
|
|
93
|
+
return { path: [...asList(exclude)] };
|
|
94
|
+
return exclude;
|
|
95
|
+
}
|
|
96
|
+
function joinedExclude(exclude) {
|
|
97
|
+
const own = excludeObject(exclude);
|
|
98
|
+
return { ...own, path: [...asList(BASE.options.exclude.path), ...asList(own.path)] };
|
|
99
|
+
}
|
|
100
|
+
function declaresAstro(manifest) {
|
|
101
|
+
if (typeof manifest !== "object" || manifest === null)
|
|
102
|
+
return false;
|
|
103
|
+
return ["dependencies", "devDependencies"].some((field) => {
|
|
104
|
+
const declared = Reflect.get(manifest, field);
|
|
105
|
+
return typeof declared === "object" && declared !== null && "astro" in declared;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function dependsOnAstro() {
|
|
109
|
+
return existsSync(MANIFEST) && declaresAstro(JSON.parse(readFileSync(MANIFEST, "utf8")));
|
|
110
|
+
}
|
|
111
|
+
function defineConfig({ devOnly = DEV_ONLY, orphans = [], forbidden = [], options, ...rest } = {}) {
|
|
112
|
+
const named = new Set(forbidden.map(({ name }) => name));
|
|
113
|
+
if (dependsOnAstro())
|
|
114
|
+
named.add("no-orphans");
|
|
115
|
+
const kitRules = BASE.forbidden.filter(({ name }) => !named.has(name)).map((rule) => kitRule(rule, devOnly, orphans));
|
|
116
|
+
return {
|
|
117
|
+
...rest,
|
|
118
|
+
forbidden: [...kitRules, ...forbidden],
|
|
119
|
+
options: { ...BASE.options, ...options, exclude: joinedExclude(options?.exclude) }
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
DEV_ONLY,
|
|
124
|
+
base,
|
|
125
|
+
defineConfig
|
|
126
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { KnipConfiguration } from "knip";
|
|
2
|
+
export type ChecksKnipConfig = Omit<KnipConfiguration, "entry" | "include"> & {
|
|
3
|
+
readonly entry: readonly string[];
|
|
4
|
+
};
|
|
5
|
+
export declare const DEFAULT_ENTRY: readonly ["tests/**/*.test.ts", "dependency-cruiser.config.ts"];
|
|
6
|
+
export declare function defineConfig({ entry, ...rest }: ChecksKnipConfig): KnipConfiguration;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/quality/presets/knip.ts
|
|
2
|
+
var DEFAULT_ENTRY = ["tests/**/*.test.ts", "dependency-cruiser.config.ts"];
|
|
3
|
+
var REFUSED_ENTRY = "@avi2dg/checks/knip: set entry to the files nothing imports, or [] when package.json scripts and tests name them all";
|
|
4
|
+
function isGlobList(value) {
|
|
5
|
+
return Array.isArray(value) && value.every((glob) => typeof glob === "string" && glob !== "");
|
|
6
|
+
}
|
|
7
|
+
function defineConfig({ entry, ...rest }) {
|
|
8
|
+
if (!isGlobList(entry))
|
|
9
|
+
throw new TypeError(REFUSED_ENTRY);
|
|
10
|
+
return { include: ["files"], ...rest, entry: [...new Set([...entry, ...DEFAULT_ENTRY])] };
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
DEFAULT_ENTRY,
|
|
14
|
+
defineConfig
|
|
15
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { OxlintConfig, OxlintOverride } from "oxlint";
|
|
2
|
+
type Globs = readonly [string, ...string[]];
|
|
3
|
+
export type EffectScope = boolean | {
|
|
4
|
+
readonly files?: Globs;
|
|
5
|
+
readonly excludeFiles?: readonly string[];
|
|
6
|
+
};
|
|
7
|
+
export type ChecksConfig = Omit<OxlintConfig, "extends" | "plugins" | "jsPlugins" | "categories"> & {
|
|
8
|
+
readonly effect: EffectScope;
|
|
9
|
+
};
|
|
10
|
+
declare const SIZE_RULES: readonly ["max-lines", "max-lines-per-function", "max-statements", "readability/cognitive-complexity", "max-depth"];
|
|
11
|
+
export type SizeRule = (typeof SIZE_RULES)[number];
|
|
12
|
+
export type SizeLimits = Readonly<Record<SizeRule, number | "off">>;
|
|
13
|
+
export declare const SOURCE_LIMITS: {
|
|
14
|
+
readonly "max-lines": 400;
|
|
15
|
+
readonly "max-lines-per-function": 100;
|
|
16
|
+
readonly "max-statements": 30;
|
|
17
|
+
readonly "readability/cognitive-complexity": 15;
|
|
18
|
+
readonly "max-depth": 4;
|
|
19
|
+
};
|
|
20
|
+
export declare const TEST_LIMITS: {
|
|
21
|
+
readonly "max-lines": 600;
|
|
22
|
+
readonly "max-lines-per-function": "off";
|
|
23
|
+
readonly "max-statements": 50;
|
|
24
|
+
readonly "readability/cognitive-complexity": 15;
|
|
25
|
+
readonly "max-depth": 4;
|
|
26
|
+
};
|
|
27
|
+
export declare const SOURCES: readonly ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"];
|
|
28
|
+
export declare const TESTS: readonly ["tests/**", "**/*.test.ts", "**/*.test.tsx"];
|
|
29
|
+
export declare const base: OxlintConfig;
|
|
30
|
+
export declare function sizeBudget(files: readonly string[], limits: SizeLimits, excludeFiles?: readonly string[]): OxlintOverride;
|
|
31
|
+
export declare function effectRules(files: readonly string[], excludeFiles?: readonly string[]): OxlintOverride;
|
|
32
|
+
export declare function defineConfig({ effect, rules, overrides, options, ...rest }: ChecksConfig): OxlintConfig;
|
|
33
|
+
export {};
|