biome-ratchet 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +85 -0
- package/dist/baseline.d.ts +6 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +27 -0
- package/dist/baseline.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +60 -0
- package/dist/cli.js.map +1 -0
- package/dist/parser.d.ts +5 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +23 -0
- package/dist/parser.js.map +1 -0
- package/dist/ratchet.d.ts +12 -0
- package/dist/ratchet.d.ts.map +1 -0
- package/dist/ratchet.js +81 -0
- package/dist/ratchet.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 James Abercrombie
|
|
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
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# biome-ratchet
|
|
2
|
+
|
|
3
|
+
[](https://nodejs.org)
|
|
4
|
+
[](https://www.npmjs.com/package/biome-ratchet)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://qlty.sh/gh/Jmsa/projects/biome-ratchet)
|
|
7
|
+
[](https://qlty.sh/gh/Jmsa/projects/biome-ratchet)
|
|
8
|
+
|
|
9
|
+
Ratcheting for [Biome](https://biomejs.dev/) lint results. Prevents new violations from being introduced while allowing gradual cleanup of existing ones.
|
|
10
|
+
|
|
11
|
+
## How it works
|
|
12
|
+
|
|
13
|
+
`biome-ratchet` wraps `biome lint` and tracks violation counts per file and rule in a `biome-ratchet.json` baseline file committed to your repo. On each run:
|
|
14
|
+
|
|
15
|
+
- **New or increased violations** → exits non-zero, writes `biome-ratchet-temp.json` with the new counts
|
|
16
|
+
- **Improvements** (violations fixed) → updates `biome-ratchet.json` automatically and exits 0
|
|
17
|
+
- **No changes** → exits 0
|
|
18
|
+
|
|
19
|
+
When `biome-ratchet-temp.json` is written, review it and replace `biome-ratchet.json` with it if the increase was intentional.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
yarn add --dev biome-ratchet
|
|
25
|
+
# or
|
|
26
|
+
npm install --save-dev biome-ratchet
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Requires Node >= 22 and `@biomejs/biome` installed in your project.
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
Replace `biome lint` with `biome-ratchet lint` in your scripts:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"scripts": {
|
|
38
|
+
"lint": "biome-ratchet lint src/"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Any flags are passed through to `biome lint`.
|
|
44
|
+
|
|
45
|
+
### Initial setup
|
|
46
|
+
|
|
47
|
+
On first run with no baseline, `biome-ratchet` will fail and write `biome-ratchet-temp.json` with your current violations. Promote it to your baseline and commit it:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
cp biome-ratchet-temp.json biome-ratchet.json
|
|
51
|
+
git add biome-ratchet.json
|
|
52
|
+
git commit -m "chore: initialize biome-ratchet baseline"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
From that point on, new violations will be caught automatically.
|
|
56
|
+
|
|
57
|
+
## Baseline file
|
|
58
|
+
|
|
59
|
+
`biome-ratchet.json` tracks current violation counts per file and rule:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"src/legacy.ts": {
|
|
64
|
+
"lint/suspicious/noDoubleEquals": {
|
|
65
|
+
"error": 2
|
|
66
|
+
},
|
|
67
|
+
"lint/style/noVar": {
|
|
68
|
+
"warning": 1
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Commit this file alongside your code. When violations are fixed, the file is updated automatically.
|
|
75
|
+
|
|
76
|
+
## Environment variables
|
|
77
|
+
|
|
78
|
+
| Variable | Effect |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `RATCHET_DEFAULT_EXIT_ZERO=true` | Always exit 0 (useful for reporting without blocking) |
|
|
81
|
+
|
|
82
|
+
## Limitations (v1)
|
|
83
|
+
|
|
84
|
+
- Lint violations only — format violations are not tracked
|
|
85
|
+
- When linting a subset of files (e.g. staged files only), files that have been fully fixed won't be auto-removed from the baseline. Run against the full codebase to clean those up.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CountsMap } from "./parser";
|
|
2
|
+
export declare function loadBaseline(): CountsMap;
|
|
3
|
+
export declare function writeBaseline(data: CountsMap): void;
|
|
4
|
+
export declare function writeTempBaseline(data: CountsMap): void;
|
|
5
|
+
export declare function clearTempBaseline(): void;
|
|
6
|
+
//# sourceMappingURL=baseline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAK1C,wBAAgB,YAAY,IAAI,SAAS,CAGxC;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAEnD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAEvD;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
|
package/dist/baseline.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadBaseline = loadBaseline;
|
|
7
|
+
exports.writeBaseline = writeBaseline;
|
|
8
|
+
exports.writeTempBaseline = writeTempBaseline;
|
|
9
|
+
exports.clearTempBaseline = clearTempBaseline;
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const BASELINE_FILE = "./biome-ratchet.json";
|
|
12
|
+
const TEMP_FILE = "./biome-ratchet-temp.json";
|
|
13
|
+
function loadBaseline() {
|
|
14
|
+
if (!node_fs_1.default.existsSync(BASELINE_FILE))
|
|
15
|
+
return {};
|
|
16
|
+
return JSON.parse(node_fs_1.default.readFileSync(BASELINE_FILE, "utf8"));
|
|
17
|
+
}
|
|
18
|
+
function writeBaseline(data) {
|
|
19
|
+
node_fs_1.default.writeFileSync(BASELINE_FILE, JSON.stringify(data, null, 2));
|
|
20
|
+
}
|
|
21
|
+
function writeTempBaseline(data) {
|
|
22
|
+
node_fs_1.default.writeFileSync(TEMP_FILE, JSON.stringify(data, null, 2));
|
|
23
|
+
}
|
|
24
|
+
function clearTempBaseline() {
|
|
25
|
+
node_fs_1.default.writeFileSync(TEMP_FILE, JSON.stringify({}, null, 2));
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=baseline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.js","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":";;;;;AAMA,oCAGC;AAED,sCAEC;AAED,8CAEC;AAED,8CAEC;AArBD,sDAAyB;AAGzB,MAAM,aAAa,GAAG,sBAAsB,CAAC;AAC7C,MAAM,SAAS,GAAG,2BAA2B,CAAC;AAE9C,SAAgB,YAAY;IAC1B,IAAI,CAAC,iBAAE,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,OAAO,EAAE,CAAC;IAC7C,OAAO,IAAI,CAAC,KAAK,CAAC,iBAAE,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAc,CAAC;AACzE,CAAC;AAED,SAAgB,aAAa,CAAC,IAAe;IAC3C,iBAAE,CAAC,aAAa,CAAC,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,SAAgB,iBAAiB,CAAC,IAAe;IAC/C,iBAAE,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,SAAgB,iBAAiB;IAC/B,iBAAE,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC3D,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const parser_1 = require("./parser");
|
|
10
|
+
const baseline_1 = require("./baseline");
|
|
11
|
+
const ratchet_1 = require("./ratchet");
|
|
12
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
13
|
+
if (command !== "lint") {
|
|
14
|
+
if (!command) {
|
|
15
|
+
console.error("Usage: biome-ratchet lint [biome lint args]");
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
console.error(`Unknown command: ${command}. Only 'lint' is supported.`);
|
|
19
|
+
}
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
// Always use json reporter; strip any --reporter flag the user passed
|
|
23
|
+
const biomeArgs = [
|
|
24
|
+
"lint",
|
|
25
|
+
"--reporter=json",
|
|
26
|
+
...rest.filter((a) => !a.startsWith("--reporter")),
|
|
27
|
+
];
|
|
28
|
+
const result = (0, node_child_process_1.spawnSync)("biome", biomeArgs, {
|
|
29
|
+
encoding: "utf8",
|
|
30
|
+
stdio: ["inherit", "pipe", "inherit"],
|
|
31
|
+
});
|
|
32
|
+
if (result.error) {
|
|
33
|
+
console.error("Failed to run biome:", result.error.message);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
const stdout = result.stdout ?? "";
|
|
37
|
+
if (!stdout.trim()) {
|
|
38
|
+
console.error("biome produced no JSON output");
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
const latest = (0, parser_1.parseBiomeOutput)(stdout);
|
|
42
|
+
const baseline = (0, baseline_1.loadBaseline)();
|
|
43
|
+
const { hasNewIssues, updatedBaseline, mergedBaseline } = (0, ratchet_1.ratchet)(baseline, latest);
|
|
44
|
+
if (hasNewIssues) {
|
|
45
|
+
(0, baseline_1.writeTempBaseline)(mergedBaseline);
|
|
46
|
+
console.log(`${chalk_1.default.red("\nNew biome-ratchet issues detected!")}\nLatest results saved to ${chalk_1.default.yellow("biome-ratchet-temp.json")}.\nIf intentional, replace ${chalk_1.default.white("biome-ratchet.json")} with it and commit.`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
(0, baseline_1.writeBaseline)(updatedBaseline);
|
|
51
|
+
(0, baseline_1.clearTempBaseline)();
|
|
52
|
+
const hasImprovements = JSON.stringify(updatedBaseline) !== JSON.stringify(baseline);
|
|
53
|
+
if (hasImprovements) {
|
|
54
|
+
console.log(chalk_1.default.green("Improvements detected — baseline updated."));
|
|
55
|
+
}
|
|
56
|
+
if (process.env.RATCHET_DEFAULT_EXIT_ZERO === "true") {
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;;;;AACA,2DAA+C;AAC/C,kDAA0B;AAC1B,qCAA4C;AAC5C,yCAA+F;AAC/F,uCAAoC;AAEpC,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEjD,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;IACvB,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;IAC/D,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,oBAAoB,OAAO,6BAA6B,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,sEAAsE;AACtE,MAAM,SAAS,GAAG;IAChB,MAAM;IACN,iBAAiB;IACjB,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,GAAG,IAAA,8BAAS,EAAC,OAAO,EAAE,SAAS,EAAE;IAC3C,QAAQ,EAAE,MAAM;IAChB,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC;CACtC,CAAC,CAAC;AAEH,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;AACnC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;IACnB,OAAO,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,MAAM,GAAG,IAAA,yBAAgB,EAAC,MAAM,CAAC,CAAC;AACxC,MAAM,QAAQ,GAAG,IAAA,uBAAY,GAAE,CAAC;AAChC,MAAM,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,GAAG,IAAA,iBAAO,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAEpF,IAAI,YAAY,EAAE,CAAC;IACjB,IAAA,4BAAiB,EAAC,cAAc,CAAC,CAAC;IAClC,OAAO,CAAC,GAAG,CACT,GAAG,eAAK,CAAC,GAAG,CAAC,sCAAsC,CAAC,6BAA6B,eAAK,CAAC,MAAM,CAAC,yBAAyB,CAAC,8BAA8B,eAAK,CAAC,KAAK,CAAC,oBAAoB,CAAC,sBAAsB,CAC9M,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;KAAM,CAAC;IACN,IAAA,wBAAa,EAAC,eAAe,CAAC,CAAC;IAC/B,IAAA,4BAAiB,GAAE,CAAC;IAEpB,MAAM,eAAe,GACnB,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC/D,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC,CAAC;IACxE,CAAC;IAED,IAAI,OAAO,CAAC,GAAG,CAAC,yBAAyB,KAAK,MAAM,EAAE,CAAC;QACrD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type SeverityKey = "error" | "warning";
|
|
2
|
+
export type RuleCounts = Record<string, Partial<Record<SeverityKey, number>>>;
|
|
3
|
+
export type CountsMap = Record<string, RuleCounts>;
|
|
4
|
+
export declare function parseBiomeOutput(json: string): CountsMap;
|
|
5
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,SAAS,CAAC;AAC9C,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;AAC9E,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAYnD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAoBxD"}
|
package/dist/parser.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseBiomeOutput = parseBiomeOutput;
|
|
4
|
+
function parseBiomeOutput(json) {
|
|
5
|
+
const output = JSON.parse(json);
|
|
6
|
+
const counts = {};
|
|
7
|
+
for (const diag of output.diagnostics) {
|
|
8
|
+
if (!diag.category?.startsWith("lint/"))
|
|
9
|
+
continue;
|
|
10
|
+
const file = diag.location?.path?.file;
|
|
11
|
+
if (!file)
|
|
12
|
+
continue;
|
|
13
|
+
const rule = diag.category;
|
|
14
|
+
const severity = diag.severity === "error" || diag.severity === "fatal"
|
|
15
|
+
? "error"
|
|
16
|
+
: "warning";
|
|
17
|
+
counts[file] ??= {};
|
|
18
|
+
counts[file][rule] ??= {};
|
|
19
|
+
counts[file][rule][severity] = (counts[file][rule][severity] ?? 0) + 1;
|
|
20
|
+
}
|
|
21
|
+
return counts;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":";;AAcA,4CAoBC;AApBD,SAAgB,gBAAgB,CAAC,IAAY;IAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAoB,CAAC;IACnD,MAAM,MAAM,GAAc,EAAE,CAAC;IAE7B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACtC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,OAAO,CAAC;YAAE,SAAS;QAClD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC;QACvC,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC3B,MAAM,QAAQ,GACZ,IAAI,CAAC,QAAQ,KAAK,OAAO,IAAI,IAAI,CAAC,QAAQ,KAAK,OAAO;YACpD,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,SAAS,CAAC;QAEhB,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IACzE,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CountsMap } from "./parser";
|
|
2
|
+
export interface RatchetResult {
|
|
3
|
+
hasNewIssues: boolean;
|
|
4
|
+
updatedBaseline: CountsMap;
|
|
5
|
+
mergedBaseline: CountsMap;
|
|
6
|
+
}
|
|
7
|
+
interface Logger {
|
|
8
|
+
log: (...args: unknown[]) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function ratchet(baseline: CountsMap, latest: CountsMap, logger?: Logger): RatchetResult;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=ratchet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ratchet.d.ts","sourceRoot":"","sources":["../src/ratchet.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,UAAU,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,eAAe,EAAE,SAAS,CAAC;IAC3B,cAAc,EAAE,SAAS,CAAC;CAC3B;AAED,UAAU,MAAM;IACd,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACnC;AAwBD,wBAAgB,OAAO,CACrB,QAAQ,EAAE,SAAS,EACnB,MAAM,EAAE,SAAS,EACjB,MAAM,GAAE,MAAgB,GACvB,aAAa,CA+Df"}
|
package/dist/ratchet.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ratchet = ratchet;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const SEVERITIES = ["error", "warning"];
|
|
10
|
+
function cleanCounts(map) {
|
|
11
|
+
const result = structuredClone(map);
|
|
12
|
+
for (const file of Object.keys(result)) {
|
|
13
|
+
for (const rule of Object.keys(result[file])) {
|
|
14
|
+
for (const sev of SEVERITIES) {
|
|
15
|
+
if ((result[file][rule][sev] ?? 0) === 0) {
|
|
16
|
+
delete result[file][rule][sev];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (Object.keys(result[file][rule]).length === 0) {
|
|
20
|
+
delete result[file][rule];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (Object.keys(result[file]).length === 0) {
|
|
24
|
+
delete result[file];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
function ratchet(baseline, latest, logger = console) {
|
|
30
|
+
let hasNewIssues = false;
|
|
31
|
+
const updatedBaseline = structuredClone(baseline);
|
|
32
|
+
// Compare latest violations against baseline
|
|
33
|
+
for (const [file, rules] of Object.entries(latest)) {
|
|
34
|
+
const baselineFile = baseline[file] ?? {};
|
|
35
|
+
let fileHeaderPrinted = false;
|
|
36
|
+
const printFileHeader = () => {
|
|
37
|
+
if (!fileHeaderPrinted) {
|
|
38
|
+
logger.log(chalk_1.default.underline(file));
|
|
39
|
+
fileHeaderPrinted = true;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
for (const [rule, counts] of Object.entries(rules)) {
|
|
43
|
+
const baselineRule = baselineFile[rule] ?? {};
|
|
44
|
+
for (const severity of SEVERITIES) {
|
|
45
|
+
const current = counts[severity] ?? 0;
|
|
46
|
+
const previous = baselineRule[severity] ?? 0;
|
|
47
|
+
if (current === previous)
|
|
48
|
+
continue;
|
|
49
|
+
printFileHeader();
|
|
50
|
+
logger.log(` ${rule}`);
|
|
51
|
+
if (current > previous) {
|
|
52
|
+
hasNewIssues = true;
|
|
53
|
+
logger.log(` ${severity}: ${chalk_1.default.red(current)} (was ${chalk_1.default.yellow(previous)})`);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
logger.log(` ${severity}: ${chalk_1.default.green(current)} (was ${chalk_1.default.yellow(previous)})`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
updatedBaseline[file] = structuredClone(rules);
|
|
61
|
+
}
|
|
62
|
+
// Remove baseline entries for files that no longer exist on disk
|
|
63
|
+
for (const file of Object.keys(baseline)) {
|
|
64
|
+
if (!(file in latest) && !node_fs_1.default.existsSync(file)) {
|
|
65
|
+
delete updatedBaseline[file];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Build merged baseline: baseline + latest (for temp file)
|
|
69
|
+
const mergedBaseline = { ...structuredClone(baseline), ...structuredClone(latest) };
|
|
70
|
+
for (const file of Object.keys(baseline)) {
|
|
71
|
+
if (!(file in latest) && !node_fs_1.default.existsSync(file)) {
|
|
72
|
+
delete mergedBaseline[file];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
hasNewIssues,
|
|
77
|
+
updatedBaseline: cleanCounts(updatedBaseline),
|
|
78
|
+
mergedBaseline: cleanCounts(mergedBaseline),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=ratchet.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ratchet.js","sourceRoot":"","sources":["../src/ratchet.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "biome-ratchet",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Ratcheting applied to Biome lint results so new issues don't creep in.",
|
|
5
|
+
"main": "dist/cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"biome-ratchet": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"prepublishOnly": "tsc",
|
|
17
|
+
"test": "vitest run --coverage",
|
|
18
|
+
"test:integration": "tsc && vitest run --config vitest.integration.config.ts",
|
|
19
|
+
"test:all": "yarn test && yarn test:integration",
|
|
20
|
+
"lint": "biome lint src",
|
|
21
|
+
"lint:fix": "biome lint --write --unsafe src",
|
|
22
|
+
"release": "standard-version"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=22.0.0"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"biome",
|
|
29
|
+
"ratchet",
|
|
30
|
+
"lint"
|
|
31
|
+
],
|
|
32
|
+
"author": "James Abercrombie <jmsabercrombie88@gmail.com>",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/Jmsa/biome-ratchet.git"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"chalk": "4.1.2"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@biomejs/biome": ">=1.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@biomejs/biome": "1.9.4",
|
|
46
|
+
"@types/node": "22.13.10",
|
|
47
|
+
"@vitest/coverage-v8": "3.0.9",
|
|
48
|
+
"standard-version": "^9.5.0",
|
|
49
|
+
"typescript": "5.7.3",
|
|
50
|
+
"vitest": "3.0.9"
|
|
51
|
+
}
|
|
52
|
+
}
|