@avi2dg/checks 0.17.0 → 0.19.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 +22 -0
- package/README.md +3 -1
- package/bunfig.toml +1 -1
- package/dependency-cruiser.config.js +2 -0
- package/dist/feature-rules.js +22 -5
- package/docs/configs/commit-messages.md +2 -18
- package/docs/configs/quality-file.md +3 -2
- package/docs/gates/checks-lint.md +2 -1
- package/docs/gates/checks-mutation-compare.md +20 -11
- package/docs/gates/checks-quality.md +23 -8
- package/docs/gates/checks-quarantine-clock.md +54 -0
- package/docs/gates/checks-test-layout.md +9 -3
- package/docs/gates/checks-vendor.md +97 -0
- package/package.json +11 -2
- package/quality.schema.json +56 -4
- package/scripts/backtest.ts +57 -41
- package/scripts/ci-wiring.ts +2 -63
- package/scripts/comment-gate.ts +3 -4
- package/scripts/comment-matchers.ts +54 -57
- package/scripts/commit-identity.ts +3 -4
- package/scripts/doc-outline.ts +34 -16
- package/scripts/doc-rules.ts +34 -21
- package/scripts/docs.ts +3 -4
- package/scripts/feature-owners.ts +5 -8
- package/scripts/gates.ts +1 -0
- package/scripts/git.ts +41 -26
- package/scripts/lint.ts +75 -35
- package/scripts/mutation-compare.ts +179 -70
- package/scripts/prose-matchers.ts +93 -83
- package/scripts/quality-file.ts +30 -3
- package/scripts/quality.ts +157 -10
- package/scripts/quarantine-clock.ts +153 -0
- package/scripts/range-gate.ts +9 -0
- package/scripts/repetition.ts +40 -19
- package/scripts/shell-command.ts +74 -0
- package/scripts/size-budget.ts +51 -28
- package/scripts/suppressions-ratchet.ts +22 -28
- package/scripts/test-layout.ts +65 -52
- package/scripts/test-report.ts +18 -13
- package/scripts/vendor.ts +337 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
Every release of `@avi2dg/checks`, newest first, written by the release from its conventional commits.
|
|
4
4
|
|
|
5
|
+
## 0.19.0
|
|
6
|
+
|
|
7
|
+
Released 2026-09-26.
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **scripts:** judge checks-mutation-compare mutant by mutant instead of by score (#58)
|
|
12
|
+
- **scripts:** fail a test left in tests/quarantine past 30 days (#59)
|
|
13
|
+
- **scripts:** pin shared read-only library clones with checks-vendor (#57)
|
|
14
|
+
|
|
15
|
+
## 0.18.0
|
|
16
|
+
|
|
17
|
+
Released 2026-09-26.
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
- **scripts:** generate commitlint and CI workflows with checks-quality (#54)
|
|
22
|
+
|
|
23
|
+
### Fixes
|
|
24
|
+
|
|
25
|
+
- **scripts:** make checks-quality refuse a config that drops the kit's extends (#55)
|
|
26
|
+
|
|
5
27
|
## 0.17.0
|
|
6
28
|
|
|
7
29
|
Released 2026-09-25.
|
package/README.md
CHANGED
|
@@ -123,6 +123,7 @@ A repository leaves out a gate that does not apply to it through `gates.lint`, a
|
|
|
123
123
|
| [`checks-size-budget`](docs/gates/checks-size-budget.md) | the range | a repository tracking `*.ts` or `*.tsx` |
|
|
124
124
|
| [`checks-repetition`](docs/gates/checks-repetition.md) | the range | a repository tracking `*.ts` or `*.tsx` |
|
|
125
125
|
| [`checks-feature-owners`](docs/gates/checks-feature-owners.md) | the range | a repository tracking `*.ts` or `*.tsx` |
|
|
126
|
+
| [`checks-quarantine-clock`](docs/gates/checks-quarantine-clock.md) | the range | every repository |
|
|
126
127
|
|
|
127
128
|
<!-- end generated gates -->
|
|
128
129
|
|
|
@@ -130,8 +131,9 @@ These bins run on their own:
|
|
|
130
131
|
|
|
131
132
|
- [`checks-test`](docs/gates/checks-test.md) runs the suite as `scripts.test` and refuses a skip the repository has not declared.
|
|
132
133
|
- [`checks-flake`](docs/gates/checks-flake.md) runs the suite on a schedule and records the seeds a flaky test fails with.
|
|
133
|
-
- [`checks-mutation-compare`](docs/gates/checks-mutation-compare.md) holds a pull request
|
|
134
|
+
- [`checks-mutation-compare`](docs/gates/checks-mutation-compare.md) holds every mutant in a pull request to no regression.
|
|
134
135
|
- [`checks-backtest`](docs/gates/checks-backtest.md) reports what the comment check would have refused in recent history.
|
|
136
|
+
- [`checks-vendor`](docs/gates/checks-vendor.md) pins each library `quality.json` declares to a shared read-only clone and links it under `repos/`.
|
|
135
137
|
|
|
136
138
|
`checks-lint` has [its own page](docs/gates/checks-lint.md), which says which range it resolves.
|
|
137
139
|
The oxlint base, the dependency-cruiser base and the commitlint config run through their own tools, as the pages under Related topics say.
|
package/bunfig.toml
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
[test]
|
|
2
|
-
pathIgnorePatterns = ["**/tests/quarantine/**"]
|
|
2
|
+
pathIgnorePatterns = ["**/tests/quarantine/**", "repos/**"]
|
|
@@ -72,6 +72,8 @@ export default {
|
|
|
72
72
|
parser: "swc",
|
|
73
73
|
builtInModules: { add: ["bun"] },
|
|
74
74
|
doNotFollow: { path: ["node_modules"] },
|
|
75
|
+
// The cruise follows the repos/ links without this.
|
|
76
|
+
exclude: { path: "^repos/" },
|
|
75
77
|
enhancedResolveOptions: {
|
|
76
78
|
exportsFields: ["exports"],
|
|
77
79
|
conditionNames: ["types", "import", "require", "node", "default"],
|
package/dist/feature-rules.js
CHANGED
|
@@ -21,7 +21,8 @@ var KIT_GATES = [
|
|
|
21
21
|
{ bin: "checks-quality", script: "quality.ts", reads: "tree", args: ["--check"], appliesTo: QUALITY_DECLARATION },
|
|
22
22
|
{ bin: "checks-size-budget", script: "size-budget.ts", reads: "range", appliesTo: TYPESCRIPT_SOURCE },
|
|
23
23
|
{ bin: "checks-repetition", script: "repetition.ts", reads: "range", appliesTo: TYPESCRIPT_SOURCE },
|
|
24
|
-
{ bin: "checks-feature-owners", script: "feature-owners.ts", reads: "range", appliesTo: TYPESCRIPT_SOURCE }
|
|
24
|
+
{ bin: "checks-feature-owners", script: "feature-owners.ts", reads: "range", appliesTo: TYPESCRIPT_SOURCE },
|
|
25
|
+
{ bin: "checks-quarantine-clock", script: "quarantine-clock.ts", reads: "range", appliesTo: EVERY_REPOSITORY }
|
|
25
26
|
];
|
|
26
27
|
var UNCONDITIONAL = KIT_GATES.filter((gate) => gate.appliesTo === EVERY_REPOSITORY).map((gate) => gate.bin);
|
|
27
28
|
var LintGates = Schema.Array(Schema.Literals(KIT_GATES.map((gate) => gate.bin))).check(Schema.makeFilter((selected) => {
|
|
@@ -158,8 +159,21 @@ var EffectSources = Schema3.Struct({
|
|
|
158
159
|
}),
|
|
159
160
|
exempt: Schema3.optionalKey(Schema3.Array(PathGlob).annotate({ description: "Files under paths the Effect rules pass over" }))
|
|
160
161
|
});
|
|
162
|
+
var Library = Schema3.Struct({
|
|
163
|
+
name: Schema3.String.check(Schema3.isPattern(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, { expected: "a library name in kebab case" })).annotate({ description: "The link checks-vendor manages under repos/" }),
|
|
164
|
+
package: Schema3.NonEmptyString.annotate({ description: "The npm package whose installed version picks the tag" }),
|
|
165
|
+
repository: Schema3.NonEmptyString.annotate({ description: "The git remote checks-vendor clones the tag from" }),
|
|
166
|
+
tag: Schema3.String.check(Schema3.isPattern(/\{version\}/, { expected: "a tag template holding {version}" })).annotate({
|
|
167
|
+
description: "The tag template, with {version} for the installed version"
|
|
168
|
+
}),
|
|
169
|
+
path: Schema3.optionalKey(FilePath.annotate({ description: "The manifest inside the clone holding the version; package.json when absent" }))
|
|
170
|
+
}).annotate({ identifier: "Library" });
|
|
161
171
|
var Sources = Schema3.Struct({
|
|
162
172
|
production: Schema3.optionalKey(Schema3.Array(PathGlob).annotate({ description: "The source the repository ships, as against tests and tooling" })),
|
|
173
|
+
libraries: Schema3.optionalKey(Schema3.Array(Library).check(Schema3.makeFilter((libraries) => {
|
|
174
|
+
const repeated = duplicates(libraries.map((library) => library.name));
|
|
175
|
+
return repeated === undefined || `names ${repeated} more than once`;
|
|
176
|
+
})).annotate({ description: "The libraries checks-vendor pins to a shared read-only clone" })),
|
|
163
177
|
effect: Schema3.optionalKey(EffectSources)
|
|
164
178
|
});
|
|
165
179
|
var Feature = Schema3.Struct({
|
|
@@ -179,11 +193,14 @@ var Feature = Schema3.Struct({
|
|
|
179
193
|
function nests(outer, inner) {
|
|
180
194
|
return outer === inner || inner.startsWith(`${outer}/`);
|
|
181
195
|
}
|
|
182
|
-
|
|
183
|
-
const names = features.map((feature) => feature.name);
|
|
196
|
+
function duplicates(names) {
|
|
184
197
|
const repeated = names.filter((name, index) => names.indexOf(name) !== index);
|
|
185
|
-
|
|
186
|
-
|
|
198
|
+
return repeated.length === 0 ? undefined : [...new Set(repeated)].join(", ");
|
|
199
|
+
}
|
|
200
|
+
var Features = Schema3.Array(Feature).check(Schema3.makeFilter((features) => {
|
|
201
|
+
const repeated = duplicates(features.map((feature) => feature.name));
|
|
202
|
+
if (repeated !== undefined)
|
|
203
|
+
return `names ${repeated} more than once`;
|
|
187
204
|
for (const outer of features) {
|
|
188
205
|
const inner = features.find((other) => other !== outer && nests(outer.root, other.root));
|
|
189
206
|
if (inner !== undefined)
|
|
@@ -10,24 +10,8 @@ It arrives with the kit, since `@commitlint/cli` and `@commitlint/config-convent
|
|
|
10
10
|
## Workflow
|
|
11
11
|
|
|
12
12
|
The lint runs in CI on pull requests, because `jj` never fires a git hook.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```yaml
|
|
16
|
-
on:
|
|
17
|
-
pull_request:
|
|
18
|
-
types: [opened, edited, synchronize, reopened]
|
|
19
|
-
jobs:
|
|
20
|
-
commitlint:
|
|
21
|
-
runs-on: ubuntu-latest
|
|
22
|
-
steps:
|
|
23
|
-
- uses: actions/checkout@v5
|
|
24
|
-
- uses: oven-sh/setup-bun@v2
|
|
25
|
-
- run: bun install --frozen-lockfile
|
|
26
|
-
- run: printf '%s' "$PR_TITLE (#0000)" > "$RUNNER_TEMP/pr-title"
|
|
27
|
-
env:
|
|
28
|
-
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
29
|
-
- run: ./node_modules/.bin/commitlint --config ./node_modules/@avi2dg/checks/commitlint.config.js --edit "$RUNNER_TEMP/pr-title"
|
|
30
|
-
```
|
|
13
|
+
`checks-quality generate` writes the workflow whole into `.github/workflows/commitlint.yml`, as [checks-quality](../gates/checks-quality.md) says.
|
|
14
|
+
The workflow lints with the installed kit's `commitlint.config.js`, so every repository holds titles to the same rules.
|
|
31
15
|
|
|
32
16
|
## What it lints
|
|
33
17
|
|
|
@@ -39,13 +39,14 @@ The kit's bins find the file at the git root and read it there:
|
|
|
39
39
|
|
|
40
40
|
| Key | Read by | Holds |
|
|
41
41
|
| --- | --- | --- |
|
|
42
|
-
| `defaultBranch` | `checks-lint`, `checks-ci-wiring` | the branch pull requests merge into, `main` when absent |
|
|
43
|
-
| `gates.ci` | `checks-ci-wiring` | the commands CI runs on every pull request, as [checks-ci-wiring](../gates/checks-ci-wiring.md) says |
|
|
42
|
+
| `defaultBranch` | `checks-lint`, `checks-ci-wiring`, `checks-quality` | the branch pull requests merge into, `main` when absent |
|
|
43
|
+
| `gates.ci` | `checks-ci-wiring`, `checks-quality` | the commands CI runs on every pull request, as [checks-ci-wiring](../gates/checks-ci-wiring.md) says |
|
|
44
44
|
| `gates.scheduled` | `checks-ci-wiring` | the commands a schedule runs |
|
|
45
45
|
| `gates.lint` | `checks-lint`, `checks-ci-wiring` | the gates `checks-lint` runs when not all apply, as [Gate selection](../gates/checks-lint.md#gate-selection) says |
|
|
46
46
|
| `commitIdentity.authors` | `checks-commit-identity` | the identities allowed to author and commit, as [checks-commit-identity](../gates/checks-commit-identity.md) says |
|
|
47
47
|
| `sources.production` | `checks-size-budget`, `checks-repetition`, `checks-quality` | the source the repository ships, as [checks-size-budget](../gates/checks-size-budget.md) and [checks-repetition](../gates/checks-repetition.md) say |
|
|
48
48
|
| `sources.effect` | `checks-quality` | the paths held to the Effect rules, and the files under them that are not, as [The Effect rules](effect-rules.md) says |
|
|
49
|
+
| `sources.libraries` | `checks-vendor`, `checks-test-layout` | the libraries pinned to a shared read-only clone, as [checks-vendor](../gates/checks-vendor.md) says |
|
|
49
50
|
| `size` | `checks-size-budget` | the size budget of production and test files, and how a change is held to it, as [checks-size-budget](../gates/checks-size-budget.md) says |
|
|
50
51
|
| `features` | `featureRules`, `checks-feature-owners` | each feature's root, entries, exempt importers and proof, as [checks-feature-owners](../gates/checks-feature-owners.md) says |
|
|
51
52
|
| `changeSignal` | `checks-feature-owners` | `advisory` to list the feature owners a change touches |
|
|
@@ -64,7 +64,7 @@ It prints the range, the declared selection if there is one, each gate's own rep
|
|
|
64
64
|
```
|
|
65
65
|
checks-lint: range 2504acf098d120e73a8ece3c96f22b934f35c6a8..10ba7d8935b73ed72624120a1542e51bd21ca7c7 from HEAD against origin/main
|
|
66
66
|
...
|
|
67
|
-
checks-lint: 3 of
|
|
67
|
+
checks-lint: 3 of 12 gate(s) failed: checks-commit-identity, checks-comment-gate, checks-suppressions-ratchet
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
## Opting out
|
|
@@ -86,6 +86,7 @@ It declares the gates `checks-lint` runs as `gates.lint`:
|
|
|
86
86
|
"checks-suppressions-ratchet",
|
|
87
87
|
"checks-ci-wiring",
|
|
88
88
|
"checks-docs",
|
|
89
|
+
"checks-quarantine-clock",
|
|
89
90
|
"checks-quality"
|
|
90
91
|
]
|
|
91
92
|
}
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
# checks-mutation-compare
|
|
2
2
|
|
|
3
|
-
`checks-mutation-compare` is the gate that holds a pull request
|
|
3
|
+
`checks-mutation-compare` is the gate that holds every mutant in a pull request to no regression rather than an absolute score, and a reader looks it up to wire mutation testing into CI.
|
|
4
4
|
|
|
5
5
|
## What it checks
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
The
|
|
9
|
-
|
|
7
|
+
A mutant regresses when it is `Killed` or `Timeout` at the base and `Survived` or `NoCoverage` at the head.
|
|
8
|
+
The gate matches mutants between the two reports and judges each match, so a lost kill cannot hide behind mutants that move into the score and a mutant leaving the score cannot manufacture a false regression.
|
|
9
|
+
A matched mutant that moves into or out of `RuntimeError`, `CompileError`, `Ignored` or `Pending` is reported apart from a regression, because that move only changes which mutants leave the score.
|
|
10
|
+
A mutant present in only one report is listed and never fails the comparison.
|
|
11
|
+
|
|
12
|
+
## How it matches mutants
|
|
13
|
+
|
|
14
|
+
The gate aligns each file's base and head source, which it reads from the report's copy of the file, by a line diff.
|
|
15
|
+
It maps each base mutant to the place its lines moved to in the head, so a line added or removed above a mutant leaves it matched.
|
|
16
|
+
It then matches a mutant in the base report to a mutant in the head report by its file, that mapped location, its mutator and its replacement.
|
|
17
|
+
Two mutants in the same report can share all four, so the gate also counts the position of each mutant among others with the same four, in the order the report lists them, and adds that position to the key.
|
|
18
|
+
A mutant on a line the pull request added, removed or changed has no counterpart in the other report and lands in the unmatched list.
|
|
19
|
+
Each list prints in order of file, then line, then column, and a matched mutant prints at its place in the head.
|
|
10
20
|
|
|
11
21
|
## What it reads
|
|
12
22
|
|
|
@@ -37,19 +47,18 @@ checks-mutation-compare [--advisory] <base-report> <head-report>
|
|
|
37
47
|
|
|
38
48
|
| Code | When |
|
|
39
49
|
| --- | --- |
|
|
40
|
-
| 0 |
|
|
41
|
-
| 1 |
|
|
50
|
+
| 0 | no mutant regressed, or `--advisory` is given |
|
|
51
|
+
| 1 | a mutant regressed |
|
|
42
52
|
| 2 | a report is not a Stryker mutation report, or the arguments do not parse |
|
|
43
53
|
|
|
44
54
|
## Sample output
|
|
45
55
|
|
|
46
|
-
It prints the
|
|
56
|
+
It prints the verdict first, then each regression, each move into or out of a status that leaves the score, and each unmatched mutant:
|
|
47
57
|
|
|
48
58
|
```
|
|
49
|
-
mutation-compare:
|
|
50
|
-
src/billing.ts:
|
|
51
|
-
1
|
|
52
|
-
mutation-compare: REGRESSION (-16.67pp)
|
|
59
|
+
mutation-compare: REGRESSION (1 mutant(s))
|
|
60
|
+
regression src/billing.ts:12:5 ConditionalExpression "true": Killed -> Survived
|
|
61
|
+
moved src/loader.ts:4:1 ClassDeclaration "class {}": Killed -> RuntimeError
|
|
53
62
|
```
|
|
54
63
|
|
|
55
64
|
## Opting out
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# checks-quality
|
|
2
2
|
|
|
3
|
-
`checks-quality` is the bin that writes what `quality.json` declares
|
|
3
|
+
`checks-quality` is the bin that writes what `quality.json` declares into generated fragments and workflows and checks them, and a reader looks it up when generated text is stale.
|
|
4
4
|
|
|
5
5
|
## What it checks
|
|
6
6
|
|
|
@@ -29,11 +29,20 @@ A rule only this repository needs stays in its own `.oxlintrc.json`, whose overr
|
|
|
29
29
|
}
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
GitHub Actions reads its own YAML and nothing else, so `checks-quality generate` also writes the kit recipe workflows whole.
|
|
33
|
+
`.github/workflows/ci.yml` runs every `gates.ci` command but the title lint as its own step after a frozen install.
|
|
34
|
+
`.github/workflows/commitlint.yml` lints the pull request title with the installed kit config.
|
|
35
|
+
A step one repository alone needs lives in another workflow file, never in the recipe.
|
|
36
|
+
All generated workflows are committed.
|
|
37
|
+
|
|
32
38
|
`checks-quality --check` fails when any of these holds:
|
|
33
39
|
|
|
34
40
|
- A fragment is missing, or differs from what `generate` would write from `quality.json` and the installed kit's presets.
|
|
35
41
|
- A fragment is left over once `quality.json` stops declaring `sources.effect`.
|
|
42
|
+
- A generated workflow is missing, or differs from what `generate` would write from `quality.json` and the kit recipe.
|
|
36
43
|
- `.oxlintrc.json` or `tsconfig.json` does not list its fragment in `extends`, so the tool never reads it.
|
|
44
|
+
- `.oxlintrc.json` does not extend `./node_modules/@avi2dg/checks/oxlintrc.json`, so the kit's oxlint rules are not loaded.
|
|
45
|
+
- `tsconfig.json` does not extend `@avi2dg/checks/tsconfig.effect.json`, the one accepted spelling of the kit's Effect config.
|
|
37
46
|
- A `sources.effect.paths` or `sources.production` glob matches no tracked or untracked file, so it holds nothing.
|
|
38
47
|
|
|
39
48
|
Two details of the fragments are easy to get wrong, so the kit's tests pin both:
|
|
@@ -47,7 +56,10 @@ Two details of the fragments are easy to get wrong, so the kit's tests pin both:
|
|
|
47
56
|
|
|
48
57
|
## What it reads
|
|
49
58
|
|
|
50
|
-
It reads the working tree: `quality.json`, the presets of the installed kit, `.oxlintrc.json`, `tsconfig.json
|
|
59
|
+
It reads the working tree: `quality.json`, the presets of the installed kit, `.oxlintrc.json`, `tsconfig.json`, the two fragments and the generated workflows.
|
|
60
|
+
It reads the root `package.json` name, since only the kit's own tree lints titles with its root `commitlint.config.js`.
|
|
61
|
+
The name also decides which kit configs `extends` must list, since the kit's own tree extends its root `oxlintrc.json` and `tsconfig.effect.json`.
|
|
62
|
+
It looks for `.bun-version`, and the suite pins its bun to that file when the file exists.
|
|
51
63
|
It lists the tracked and untracked files to see what each declared glob matches.
|
|
52
64
|
|
|
53
65
|
## Arguments
|
|
@@ -57,15 +69,15 @@ checks-quality generate
|
|
|
57
69
|
checks-quality --check
|
|
58
70
|
```
|
|
59
71
|
|
|
60
|
-
`generate` writes the fragments, removes a left-over one, then runs the same check as `--check`.
|
|
72
|
+
`generate` writes the fragments and the workflows, removes a left-over one, then runs the same check as `--check`.
|
|
61
73
|
`--check` writes nothing.
|
|
62
74
|
|
|
63
75
|
## Exit codes
|
|
64
76
|
|
|
65
77
|
| Code | When |
|
|
66
78
|
| --- | --- |
|
|
67
|
-
| 0 | the
|
|
68
|
-
| 1 | a
|
|
79
|
+
| 0 | the generated files hold what `quality.json` declares |
|
|
80
|
+
| 1 | a generated file is stale, missing or left over, a fragment is not extended, the kit's config is not extended, or a declared glob matches no file |
|
|
69
81
|
| 2 | `quality.json` does not decode, or the arguments are neither `generate` nor `--check` |
|
|
70
82
|
|
|
71
83
|
## Sample output
|
|
@@ -76,16 +88,19 @@ checks-quality: 2 problem(s) with what quality.json declares:
|
|
|
76
88
|
tsconfig.json does not extend ./tsconfig.quality.json, so the language service never reads it
|
|
77
89
|
```
|
|
78
90
|
|
|
79
|
-
A passing run says what the
|
|
91
|
+
A passing run says what the generated files hold:
|
|
80
92
|
|
|
81
93
|
```
|
|
82
|
-
checks-quality: oxlintrc.quality.json and tsconfig.quality.json hold what quality.json declares
|
|
94
|
+
checks-quality: oxlintrc.quality.json and tsconfig.quality.json and .github/workflows/ci.yml and .github/workflows/commitlint.yml hold what quality.json declares
|
|
83
95
|
```
|
|
84
96
|
|
|
97
|
+
A stale workflow is reported the same way as a stale fragment.
|
|
98
|
+
|
|
85
99
|
## Opting out
|
|
86
100
|
|
|
87
101
|
It runs only in a repository that tracks `quality.json`, and a selection in `quality.json` always keeps it, since the file it sits in is what makes it apply.
|
|
88
|
-
A repository that declares no `sources.effect` gets no fragment, and the check then
|
|
102
|
+
A repository that declares no `sources.effect` gets no fragment, and the check then refuses a left-over one.
|
|
103
|
+
Every repository gets the title lint workflow, and one with `gates.ci` gets the suite with it.
|
|
89
104
|
|
|
90
105
|
## Related topics
|
|
91
106
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# checks-quarantine-clock
|
|
2
|
+
|
|
3
|
+
`checks-quarantine-clock` is the gate that fails a test left in `tests/quarantine/` past 30 days, and a reader looks it up when a quarantined test went red.
|
|
4
|
+
|
|
5
|
+
## What it checks
|
|
6
|
+
|
|
7
|
+
It fails naming each test that entered `tests/quarantine/` more than 30 days before the head.
|
|
8
|
+
`checks-test-layout` pins `tests/quarantine/` out of every default run, so a test there protects nothing until it moves back.
|
|
9
|
+
Each failure names the file, the day it entered quarantine, and what to do, which is to fix it and move it back, or delete it.
|
|
10
|
+
The limit is 30 days for every test, with no setting to raise it.
|
|
11
|
+
GitLab quarantines fast for 3 days and long term for at most 3 months, then opens a deletion merge request automatically.
|
|
12
|
+
|
|
13
|
+
## What it reads
|
|
14
|
+
|
|
15
|
+
It lists the test files under `tests/quarantine/` at the head, by the name pattern `checks-test-layout` uses, so a helper or fixture there never ages out.
|
|
16
|
+
It walks each file's history with `git log --follow`, so a move or a copy into quarantine starts the clock there rather than at the test's creation.
|
|
17
|
+
It measures the age from the author date of the commit that put the file there to the later of the head's author and committer dates, so the same commit always gets the same verdict.
|
|
18
|
+
A rebase keeps the entry's author date and moves the head's committer date forward, so rebasing neither restarts the clock nor stops it.
|
|
19
|
+
|
|
20
|
+
## Arguments
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
checks-quarantine-clock <base-ref> <head-ref>
|
|
24
|
+
checks-quarantine-clock <ref>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
With two arguments it judges the head, and the base only satisfies the range `checks-lint` hands every range gate.
|
|
28
|
+
With one it judges that commit, including a repository's first commit.
|
|
29
|
+
|
|
30
|
+
## Exit codes
|
|
31
|
+
|
|
32
|
+
| Code | When |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| 0 | no test in `tests/quarantine/` is past 30 days |
|
|
35
|
+
| 1 | a test in `tests/quarantine/` is past 30 days |
|
|
36
|
+
| 2 | a ref does not resolve, or the history ends before a file's entry into quarantine, as in a shallow clone |
|
|
37
|
+
|
|
38
|
+
## Sample output
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
quarantine-clock: 1 test(s) in tests/quarantine/ is past 30 days; fix each and move it back, or delete it:
|
|
42
|
+
tests/quarantine/billing.test.ts entered quarantine on 2026-08-01 (45 days ago)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Opting out
|
|
46
|
+
|
|
47
|
+
It applies to every repository, so no selection leaves it out.
|
|
48
|
+
A repository with no test file under `tests/quarantine/` passes with nothing checked.
|
|
49
|
+
`checks-lint` runs it over each pull request's range, as [checks-lint](checks-lint.md) says.
|
|
50
|
+
|
|
51
|
+
## Related topics
|
|
52
|
+
|
|
53
|
+
- [checks-test-layout](checks-test-layout.md)
|
|
54
|
+
- [checks-lint](checks-lint.md)
|
|
@@ -18,19 +18,23 @@ It fails unless the repository holds this shape, and names the file and the path
|
|
|
18
18
|
- `scripts.test` is exactly `checks-test`, which runs `bun test --randomize` as [checks-test](checks-test.md) says.
|
|
19
19
|
- `scripts.lint` runs this check, itself or through `checks-lint` called by its bare bin name.
|
|
20
20
|
- `bunfig.toml` carries every `[test]` key of the shipped preset with the same value.
|
|
21
|
-
`[test].pathIgnorePatterns` is
|
|
21
|
+
`[test].pathIgnorePatterns` is the preset's `["**/tests/quarantine/**", "repos/**"]`, which the check pins itself, so the kit's own repository, whose bunfig is the preset, cannot drift it either.
|
|
22
|
+
A repository whose `quality.json` declares no `sources.libraries` may hold `["**/tests/quarantine/**"]` instead, and one that declares them must keep `repos/**`.
|
|
22
23
|
Other tables, and extra `[test]` keys, are the repository's own.
|
|
23
24
|
|
|
24
25
|
The in-process half is what a mutation run can mutate.
|
|
25
26
|
`tests/e2e/**` is left out of a mutate scope by construction, because a subprocess kills both the speed and the coverage signal a mutant needs.
|
|
26
27
|
|
|
27
|
-
The preset also skips `tests/quarantine/**` on a default run.
|
|
28
|
+
The preset also skips `tests/quarantine/**` and `repos/**` on a default run.
|
|
29
|
+
The `repos/**` entry keeps the suite from following the library links `checks-vendor` manages into trees whose tests are not this repository's.
|
|
28
30
|
A test that turns flaky moves there, so the suite stays trustworthy, and the flake still runs on demand:
|
|
29
31
|
|
|
30
32
|
```sh
|
|
31
33
|
bun test --path-ignore-patterns='' tests/quarantine
|
|
32
34
|
```
|
|
33
35
|
|
|
36
|
+
A test left there past 30 days fails [checks-quarantine-clock](checks-quarantine-clock.md).
|
|
37
|
+
|
|
34
38
|
## What it reads
|
|
35
39
|
|
|
36
40
|
It reads the working tree.
|
|
@@ -38,6 +42,7 @@ It scans the tracked and untracked files that `git ls-files --exclude-standard`
|
|
|
38
42
|
It parses each test and helper with swc and reads import specifiers and identifier use, so a test that only carries `"node:child_process"` as a string is not a violation.
|
|
39
43
|
`tests/fixtures/**` is data and is not parsed.
|
|
40
44
|
It reads `package.json` for `scripts.test` and `scripts.lint`, and compares `bunfig.toml` with the preset the installed kit ships.
|
|
45
|
+
It reads `quality.json` for `sources.libraries`, which decides whether `repos/**` is pinned.
|
|
41
46
|
|
|
42
47
|
## Arguments
|
|
43
48
|
|
|
@@ -53,7 +58,7 @@ It checks the directory it runs in, or the directory it is given.
|
|
|
53
58
|
| --- | --- |
|
|
54
59
|
| 0 | the repository holds the layout |
|
|
55
60
|
| 1 | a file breaks the layout |
|
|
56
|
-
| 2 | a test, a helper or `
|
|
61
|
+
| 2 | a test, a helper, `package.json` or `quality.json` does not parse |
|
|
57
62
|
|
|
58
63
|
## Sample output
|
|
59
64
|
|
|
@@ -75,3 +80,4 @@ A repository that tracks one keeps it.
|
|
|
75
80
|
- [checks-test](checks-test.md)
|
|
76
81
|
- [checks-flake](checks-flake.md)
|
|
77
82
|
- [checks-mutation-compare](checks-mutation-compare.md)
|
|
83
|
+
- [checks-quarantine-clock](checks-quarantine-clock.md)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# checks-vendor
|
|
2
|
+
|
|
3
|
+
`checks-vendor` pins each library `quality.json` declares to one shared read-only clone on the machine and links it under `repos/`.
|
|
4
|
+
|
|
5
|
+
## What it checks
|
|
6
|
+
|
|
7
|
+
Each entry under `sources.libraries` names an npm package, a git remote and a tag template holding `{version}`.
|
|
8
|
+
Its `name` is the link under `repos/`, and its optional `path` is the manifest inside the clone that holds the version, `package.json` when absent.
|
|
9
|
+
`checks-vendor` reads the installed version from `node_modules/<package>/package.json` and resolves the template to one tag.
|
|
10
|
+
It clones that tag once into a cache shared across repositories, records the landed commit in `<tag>.commit` beside the tree, strips every write bit and links `repos/<name>` to the tree.
|
|
11
|
+
The clone is staged beside its cache entry and moves into place only once it is recorded, checked and read only, so a concurrent or killed first run never leaves a half built tree there.
|
|
12
|
+
Every later run verifies the link rather than trusting it, and does so without contacting the remote.
|
|
13
|
+
It confirms the tree sits on the recorded commit.
|
|
14
|
+
It confirms the manifest inside the tree still names the installed version.
|
|
15
|
+
It confirms no write bit came back and no write landed outside the recorded commit.
|
|
16
|
+
It never follows a link inside the tree, so no mode outside the cache is touched.
|
|
17
|
+
Any failed confirmation fails the run.
|
|
18
|
+
A failed library drops its `repos/<name>` link, so a reader falls back to `node_modules/<package>` rather than a tree the run could not vouch for.
|
|
19
|
+
A missing tag, an unknown installed version or a manifest naming another version fails it too.
|
|
20
|
+
A tag moved upstream after the first fetch is not followed, since a cached tree stays on its recorded commit.
|
|
21
|
+
Clearing the tree with `chmod -R u+w <dir> && rm -rf <dir>` keeps the record, so the next fetch fails when the tag now lands elsewhere.
|
|
22
|
+
A deliberate move also deletes `<tag>.commit` by hand before `checks-vendor` runs again.
|
|
23
|
+
The cache lives at `~/.cache/avi2dg-checks/repos/<host>/<owner>/<repo>/<tag>/`.
|
|
24
|
+
A read through the link resolves outside the checkout, so a reader that must stay inside the tree falls back to `node_modules/<package>`.
|
|
25
|
+
|
|
26
|
+
## What it reads
|
|
27
|
+
|
|
28
|
+
It reads the working tree.
|
|
29
|
+
That is `quality.json`, `node_modules/<package>/package.json` for each declared library and the `repos/` links.
|
|
30
|
+
It reads the shared cache outside the checkout.
|
|
31
|
+
That is each tag tree, its recorded commit and its manifest.
|
|
32
|
+
It contacts a remote only when a tag is not cached yet, to confirm the tag exists and to clone it.
|
|
33
|
+
|
|
34
|
+
## Arguments
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
checks-vendor
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
It takes no arguments, since `quality.json` names the libraries.
|
|
41
|
+
|
|
42
|
+
## Exit codes
|
|
43
|
+
|
|
44
|
+
| Code | When |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| 0 | every declared library links a verified tree, its remote could not be reached for a first fetch, or no git checkout holds the run |
|
|
47
|
+
| 1 | a tag is missing or lands elsewhere than its record, an installed version is unknown, a tree was written to, a record disagrees with its tree, a version disagrees or a link is blocked |
|
|
48
|
+
| 2 | `quality.json` does not decode, `HOME` is unset, or arguments were passed |
|
|
49
|
+
|
|
50
|
+
## Sample output
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
checks-vendor: cloned effect@4.0.0-rc.115 from https://github.com/Effect-TS/effect.git and linked repos/effect
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
A fresh fetch reports the tag it cloned and the link it made.
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
checks-vendor: repos/effect still holds effect@4.0.0-rc.115, verified against its recorded commit
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A later run reports the link it kept.
|
|
63
|
+
|
|
64
|
+
## Wiring
|
|
65
|
+
|
|
66
|
+
A consuming repository runs it from its `prepare` script, so every install pins and verifies the trees.
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{ "scripts": { "prepare": "checks-vendor" } }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
An install offline still passes.
|
|
73
|
+
A cached tree verifies with no network, and a tag that is not cached yet warns, stays unlinked and leaves readers on `node_modules/<package>` until a later install can fetch it.
|
|
74
|
+
An install outside a git checkout, or with no `git` on the path, warns once, links nothing and passes too.
|
|
75
|
+
|
|
76
|
+
It ignores the links in `.gitignore` with `repos/*`, because `repos/*/` does not match a link.
|
|
77
|
+
|
|
78
|
+
```gitignore
|
|
79
|
+
repos/*
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
TypeScript does not read `.gitignore`, and its default `include` follows the links into each library tree.
|
|
83
|
+
A consumer whose `tsconfig.json` has no explicit `include` keeps the trees out with an `exclude` entry.
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{ "exclude": ["node_modules", "repos"] }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Opting out
|
|
90
|
+
|
|
91
|
+
It runs only in a repository that declares `sources.libraries`.
|
|
92
|
+
A repository without that key reports nothing to pin and changes nothing.
|
|
93
|
+
A repository that declares no library needs no `prepare` entry for it.
|
|
94
|
+
|
|
95
|
+
## Related topics
|
|
96
|
+
|
|
97
|
+
- [The quality file](../configs/quality-file.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avi2dg/checks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Deterministic checks shared across the captain's TypeScript repos",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"scripts/commit-identity.ts",
|
|
33
33
|
"scripts/mutation-compare.ts",
|
|
34
34
|
"scripts/ci-wiring.ts",
|
|
35
|
+
"scripts/shell-command.ts",
|
|
35
36
|
"scripts/comment-matchers.ts",
|
|
36
37
|
"scripts/prose-matchers.ts",
|
|
37
38
|
"scripts/comments.ts",
|
|
@@ -41,10 +42,13 @@
|
|
|
41
42
|
"scripts/quality-file.ts",
|
|
42
43
|
"scripts/quality.ts",
|
|
43
44
|
"scripts/size-budget.ts",
|
|
45
|
+
"scripts/range-gate.ts",
|
|
44
46
|
"scripts/size-rules.ts",
|
|
45
47
|
"scripts/repetition.ts",
|
|
46
48
|
"scripts/feature-owners.ts",
|
|
49
|
+
"scripts/quarantine-clock.ts",
|
|
47
50
|
"scripts/docs.ts",
|
|
51
|
+
"scripts/vendor.ts",
|
|
48
52
|
"scripts/doc-outline.ts",
|
|
49
53
|
"scripts/doc-rules.ts",
|
|
50
54
|
"scripts/doc-references.ts",
|
|
@@ -83,7 +87,9 @@
|
|
|
83
87
|
"./scripts/size-budget.ts": "./scripts/size-budget.ts",
|
|
84
88
|
"./scripts/repetition.ts": "./scripts/repetition.ts",
|
|
85
89
|
"./scripts/feature-owners.ts": "./scripts/feature-owners.ts",
|
|
90
|
+
"./scripts/quarantine-clock.ts": "./scripts/quarantine-clock.ts",
|
|
86
91
|
"./scripts/docs.ts": "./scripts/docs.ts",
|
|
92
|
+
"./scripts/vendor.ts": "./scripts/vendor.ts",
|
|
87
93
|
"./templates/readme.md": "./templates/readme.md",
|
|
88
94
|
"./templates/changelog.md": "./templates/changelog.md",
|
|
89
95
|
"./templates/adr.md": "./templates/adr.md",
|
|
@@ -118,9 +124,12 @@
|
|
|
118
124
|
"checks-size-budget": "scripts/size-budget.ts",
|
|
119
125
|
"checks-repetition": "scripts/repetition.ts",
|
|
120
126
|
"checks-feature-owners": "scripts/feature-owners.ts",
|
|
121
|
-
"checks-
|
|
127
|
+
"checks-quarantine-clock": "scripts/quarantine-clock.ts",
|
|
128
|
+
"checks-docs": "scripts/docs.ts",
|
|
129
|
+
"checks-vendor": "scripts/vendor.ts"
|
|
122
130
|
},
|
|
123
131
|
"scripts": {
|
|
132
|
+
"prepare": "bun scripts/vendor.ts",
|
|
124
133
|
"build": "bun build effect-channel/index.ts --outdir dist --target node --format esm && bun build scripts/feature-rules.ts --outdir dist --target node --format esm --packages external && bun scripts/quality-schema.ts && bun scripts/doc-templates-write.ts && bun scripts/changelog-write.ts && bun scripts/doc-blocks-write.ts",
|
|
125
134
|
"lint": "oxlint --type-aware && bun scripts/lint.ts && depcruise --config .dependency-cruiser.cjs .",
|
|
126
135
|
"typecheck": "tsc --noEmit && effect-tsgo diagnostics --project tsconfig.json --format text --strict",
|