@geonosis/ratchet 0.1.1 → 0.1.2

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/README.md CHANGED
@@ -57,6 +57,30 @@ A counter whose command cannot run is a hard error, never a silent zero. A gate
57
57
  has not passed. Nor is a number believed on its own: `oxlintErrors` reads the finding lines AND the
58
58
  summary oxlint printed, and raises when they disagree.
59
59
 
60
+ ### The warnings-only window, and `expectFormat`
61
+
62
+ The last-resort backstop under the oxlint counters — output in no shape we know *and* a non-zero
63
+ exit means the format moved, so refuse — has one gap, and it is worth naming rather than pretending
64
+ otherwise. **oxlint exits 0 when a run found only warnings.** On such a run the backstop cannot
65
+ fire: an unrecognised format reads as zero warnings, and a zero that only shrinks is a zero the
66
+ ratchet banks by rewriting the baseline down.
67
+
68
+ `--deny-warnings` is **not** the fix. It makes warnings exit non-zero by making them errors, which
69
+ moves every warning into `oxlintErrors` and changes what both numbers mean — a bigger lie than the
70
+ one it patches, told to the whole baseline.
71
+
72
+ The fix is to say which shape you asked for:
73
+
74
+ ```jsonc
75
+ { "counter": "oxlintWarnings", "command": "npx oxlint --format=unix .", "expectFormat": "unix" }
76
+ ```
77
+
78
+ With `expectFormat` set, output that matches none of the three shapes and is not empty once npm's
79
+ own chatter is stripped (`> …` script echoes, `npm warn …`, `npm notice …` update nags, blank
80
+ lines) is refused **even on a clean exit**. `npm ERR!` is not stripped — that is npm saying the
81
+ command never ran, and it must never read as zero. Unset is the default and changes nothing, so a repo that never opts in keeps exactly
82
+ the behaviour it had. Valid values: `unix`, `agent`, `default`.
83
+
60
84
  When a number **grew**, the last ten lines of that counter's command output print under the
61
85
  `<-- REGRESSED` line, indented — so the report says what grew, not only that something did. A
62
86
  counter that reads a file rather than running a command (`lawLineCount`) prints nothing extra.
@@ -83,8 +107,8 @@ Every counter takes its `command` from the config, so the toolchain stays the re
83
107
 
84
108
  | id | counts | params |
85
109
  | --- | --- | --- |
86
- | `oxlintErrors` / `oxlintWarnings` | findings under any shape oxlint prints — `--format=unix`, the compact `agent` format, the graphical `default` — cross-checked against the tool's own summary | `command` |
87
- | `oxlintRule` | one named rule's findings; with `config`, after forcing it to `error` in a temp copy, so debt cannot grow behind a downgrade | `rule`, `config`, `command` |
110
+ | `oxlintErrors` / `oxlintWarnings` | findings under any shape oxlint prints — `--format=unix`, the compact `agent` format, the graphical `default` — cross-checked against the tool's own summary | `command`, `expectFormat` |
111
+ | `oxlintRule` | one named rule's findings, counted only on lines the run reported as findings and refused when none of them attributes itself readably; with `config`, after forcing the rule to `error` in a temp copy — `"warn"`, `"off"` and the `["off", { … }]` array form alike — so debt cannot grow behind a downgrade | `rule`, `config`, `command`, `expectFormat` |
88
112
  | `typecheckErrors` | `error TS` occurrences | `command` |
89
113
  | `testFailures` | the runner's own failure summary; throws when neither a pass nor a fail count is readable | `command` |
90
114
  | `unformattedFiles` | paths `--list-different` names that exist on disk | `command` |
@@ -112,6 +112,25 @@ var UNIX_FINDING = /^\S[^\n]*:\d+:\d+: .*\[(Error|Warning)\/[^\]\n]+\]$/gm;
112
112
  var UNIX_SUMMARY = /^(\d+) problems?$/m;
113
113
  var AGENT_FINDING = /^\S[^\n]*:\d+:\d+: (error|warning) /gm;
114
114
  var DEFAULT_SUMMARY = /^Found (\d+) warnings? and (\d+) errors?\.$/m;
115
+ var DEFAULT_FINDING = /^\s*[x!] [^\s(]+\([^)\n]+\): /m;
116
+ var FINDING_SHAPES = [UNIX_FINDING, AGENT_FINDING, DEFAULT_FINDING].map(
117
+ (shape) => new RegExp(shape.source)
118
+ );
119
+ var RULE_TOKEN = /\([^()\n]+\)/;
120
+ var findingLinesOf = (output) => output.split("\n").filter((line) => FINDING_SHAPES.some((shape) => shape.test(line)));
121
+ var FORMATS = ["agent", "default", "unix"];
122
+ var isFormat = (value) => typeof value === "string" && FORMATS.some((one) => one === value);
123
+ var expectedFormat = (counter, params) => {
124
+ const value = params.expectFormat;
125
+ if (value === void 0) return void 0;
126
+ if (isFormat(value)) return value;
127
+ throw new CounterError(
128
+ counter,
129
+ `expectFormat must be one of ${FORMATS.join(", ")} \u2014 got ${JSON.stringify(value)}`
130
+ );
131
+ };
132
+ var NOISE = /^(> .*|npm warn .*|npm notice.*|\s*)$/;
133
+ var spokenByTheTool = (output) => output.split("\n").filter((line) => !NOISE.test(line)).join("\n");
115
134
  var total = (reading) => reading.errors + reading.warnings;
116
135
  var countFindings = (output, pattern, errorToken) => {
117
136
  let errors = 0;
@@ -122,7 +141,7 @@ var countFindings = (output, pattern, errorToken) => {
122
141
  }
123
142
  return { errors, warnings };
124
143
  };
125
- var readFindings = (counter, { code, output }) => {
144
+ var readFindings = (counter, { code, output }, expect) => {
126
145
  const unix = countFindings(output, UNIX_FINDING, "Error");
127
146
  const agent = countFindings(output, AGENT_FINDING, "error");
128
147
  const problems = UNIX_SUMMARY.exec(output);
@@ -148,22 +167,42 @@ ${output.trim()}`);
148
167
  return summary;
149
168
  }
150
169
  if (total(agent) > 0) return agent;
151
- if (code === 0) return { errors: 0, warnings: 0 };
170
+ if (code === 0) {
171
+ if (expect !== void 0 && spokenByTheTool(output) !== "") {
172
+ return refuse(`asked for --format=${expect} and got output in no shape this counter reads`);
173
+ }
174
+ return { errors: 0, warnings: 0 };
175
+ }
152
176
  return refuse(`the tool exited ${code} and printed no findings and no summary`);
153
177
  };
154
178
  var oxlintErrors = {
155
179
  id: "oxlintErrors",
156
180
  run: async ({ params, run }) => {
157
181
  const command = stringParam("oxlintErrors", params, "command", DEFAULT_COMMAND);
158
- return readFindings("oxlintErrors", run(command)).errors;
182
+ const expect = expectedFormat("oxlintErrors", params);
183
+ return readFindings("oxlintErrors", run(command), expect).errors;
159
184
  }
160
185
  };
161
186
  var oxlintWarnings = {
162
187
  id: "oxlintWarnings",
163
188
  run: async ({ params, run }) => {
164
189
  const command = stringParam("oxlintWarnings", params, "command", DEFAULT_COMMAND);
165
- return readFindings("oxlintWarnings", run(command)).warnings;
190
+ const expect = expectedFormat("oxlintWarnings", params);
191
+ return readFindings("oxlintWarnings", run(command), expect).warnings;
192
+ }
193
+ };
194
+ var countRule = (result, rule, expect) => {
195
+ const reading = readFindings("oxlintRule", result, expect);
196
+ const lines = findingLinesOf(result.output);
197
+ if (total(reading) > 0 && !lines.some((line) => RULE_TOKEN.test(line))) {
198
+ throw new CounterError(
199
+ "oxlintRule",
200
+ `${total(reading)} findings, not one of them attributed to a "plugin(rule)" this counter can read \u2014 oxlint's output changed:
201
+ ${result.output.trim()}`
202
+ );
166
203
  }
204
+ const named = new RegExp(`\\(${escapeForRegex(rule)}\\)`);
205
+ return lines.filter((line) => named.test(line)).length;
167
206
  };
168
207
  var oxlintRule = {
169
208
  id: "oxlintRule",
@@ -171,19 +210,18 @@ var oxlintRule = {
171
210
  const rule = stringParam("oxlintRule", params, "rule");
172
211
  const command = stringParam("oxlintRule", params, "command", DEFAULT_COMMAND);
173
212
  const config = typeof params.config === "string" ? params.config : "";
174
- if (config === "")
175
- return countMatches(run(command).output, new RegExp(`\\(${escapeForRegex(rule)}\\)`));
213
+ const expect = expectedFormat("oxlintRule", params);
214
+ if (config === "") return countRule(run(command), rule, expect);
176
215
  const source = resolve3(cwd, config);
177
216
  if (!existsSync3(source)) throw new CounterError("oxlintRule", `no config at ${config}`);
178
217
  const strictName = `.oxlintrc.ratchet-${key}.json`;
179
218
  const strict = readFileSync2(source, "utf8").replace(
180
- new RegExp(`("[^"]*${escapeForRegex(rule)}"\\s*:\\s*)"(warn|off)"`),
219
+ new RegExp(`("[^"]*${escapeForRegex(rule)}"\\s*:\\s*\\[?\\s*)"(warn|off)"`),
181
220
  '$1"error"'
182
221
  );
183
222
  writeFileSync(resolve3(cwd, strictName), strict);
184
223
  try {
185
- const output = run(command.replace("{config}", strictName)).output;
186
- return countMatches(output, new RegExp(`\\(${escapeForRegex(rule)}\\)`));
224
+ return countRule(run(command.replace("{config}", strictName)), rule, expect);
187
225
  } finally {
188
226
  rmSync(resolve3(cwd, strictName), { force: true });
189
227
  }
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  COUNTERS,
3
3
  formatReport,
4
4
  runRatchet
5
- } from "./chunk-PUJWT3QM.js";
5
+ } from "./chunk-BTWIR7DK.js";
6
6
 
7
7
  // src/cli.ts
8
8
  var cwdFlag = process.argv.indexOf("--cwd");
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  loadConfig,
9
9
  runCommand,
10
10
  runRatchet
11
- } from "./chunk-PUJWT3QM.js";
11
+ } from "./chunk-BTWIR7DK.js";
12
12
  export {
13
13
  CONFIG_FILE,
14
14
  COUNTERS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geonosis/ratchet",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Debt as a number that may only shrink — one ratchet, pluggable counters.",
5
5
  "keywords": [
6
6
  "ratchet",