@geonosis/doctor 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,99 @@
1
1
  import {
2
2
  CHECKS,
3
+ ENVELOPES_DIR,
3
4
  formatDoctor,
4
5
  formatJson,
5
6
  runDoctor
6
- } from "./chunk-SMCRXSH5.js";
7
+ } from "./chunk-R2R2OYH3.js";
7
8
 
8
9
  // src/doctor-cli.ts
10
+ import { fstatSync, statSync } from "fs";
9
11
  import { resolve } from "path";
12
+
13
+ // src/envelope-write.ts
14
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
15
+ import { dirname, join } from "path";
16
+ import { fileURLToPath } from "url";
17
+ var envelopePath = (root, tool) => join(root, ENVELOPES_DIR, `${tool}.json`);
18
+ var UnbalancedEnvelope = class extends Error {
19
+ constructor(message) {
20
+ super(message);
21
+ this.name = "UnbalancedEnvelope";
22
+ }
23
+ };
24
+ var isCount = (value) => Number.isSafeInteger(value) && value >= 0;
25
+ var unbalancedMessage = (envelope, next) => `${envelope.tool}: considered ${envelope.considered} but accounts for ${envelope.read + envelope.refused.length + envelope.excused.length} \u2014 ${envelope.read} read + ${envelope.refused.length} refused + ${envelope.excused.length} excused. A run that has lost count of its own inputs cannot say what it measured, so no verdict was rendered and no envelope was written. Next: ${next}`;
26
+ var writeEnvelope = ({
27
+ envelope,
28
+ next,
29
+ root
30
+ }) => {
31
+ if (envelope.tool.trim() === "") {
32
+ throw new UnbalancedEnvelope(
33
+ `an envelope with no tool name cannot be filed or reported against. Next: ${next}`
34
+ );
35
+ }
36
+ if (envelope.version.trim() === "") {
37
+ throw new UnbalancedEnvelope(
38
+ `${envelope.tool}: an envelope that cannot name the build that wrote it dates nothing, and a stale one reads exactly like a fresh one. Next: ${next}`
39
+ );
40
+ }
41
+ if (!isCount(envelope.considered) || !isCount(envelope.read)) {
42
+ throw new UnbalancedEnvelope(
43
+ `${envelope.tool}: considered ${envelope.considered} and read ${envelope.read} \u2014 a census is a whole number of things, and arithmetic over anything else balances by accident. Next: ${next}`
44
+ );
45
+ }
46
+ if (envelope.considered !== envelope.read + envelope.refused.length + envelope.excused.length) {
47
+ throw new UnbalancedEnvelope(unbalancedMessage(envelope, next));
48
+ }
49
+ const at = envelopePath(root, envelope.tool);
50
+ mkdirSync(dirname(at), { recursive: true });
51
+ writeFileSync(at, `${JSON.stringify(envelope, void 0, 2)}
52
+ `);
53
+ return at;
54
+ };
55
+ var UNKNOWN = "unknown";
56
+ var versionIn = (dir) => {
57
+ try {
58
+ const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
59
+ return typeof manifest.version === "string" ? manifest.version : void 0;
60
+ } catch {
61
+ return void 0;
62
+ }
63
+ };
64
+ var versionOf = (moduleUrl) => {
65
+ let dir = dirname(fileURLToPath(moduleUrl));
66
+ for (; ; ) {
67
+ const found = versionIn(dir);
68
+ if (found !== void 0) return found;
69
+ const up = dirname(dir);
70
+ if (up === dir) return UNKNOWN;
71
+ dir = up;
72
+ }
73
+ };
74
+ var DOCTOR_TOOL = "doctor";
75
+ var DOCTOR_NEXT = "geonosis-doctor --json and count the checks it printed \u2014 this run considered every question the tool has, and a check nobody asked for is excused by name rather than left out of the denominator";
76
+ var doctorEnvelope = ({
77
+ durationMs,
78
+ report
79
+ }) => {
80
+ const ran = new Set(report.ran);
81
+ return {
82
+ considered: CHECKS.length,
83
+ durationMs,
84
+ excused: CHECKS.filter((check) => !ran.has(check)).map((check) => ({
85
+ path: check,
86
+ reason: "not asked for by --only, so this run says nothing about it"
87
+ })),
88
+ findings: report.findings,
89
+ read: report.ran.length,
90
+ refused: [],
91
+ tool: DOCTOR_TOOL,
92
+ version: versionOf(import.meta.url)
93
+ };
94
+ };
95
+
96
+ // src/doctor-cli.ts
10
97
  var ABOUT = {
11
98
  baseline: `The ratchet's numbers at HEAD against another ref. A ratchet lowers its own baseline
12
99
  when a number shrinks, so nothing inside one checkout can see a branch raise one back
@@ -94,6 +181,10 @@ plugin-route-namespaced on its package root, while the shipped corpus says acme
94
181
 
95
182
  --print-config-shape every file this reads, and the keys it reads out of each
96
183
 
184
+ Every run writes .geonosis/envelopes/doctor.json: the ten checks CONSIDERED against the ones this
185
+ run read, with the rest excused by name, so a --only run can never read as a verdict about the whole
186
+ tree. The envelope check SKIPs that one file \u2014 this run is the one writing it.
187
+
97
188
  Exits 1 when any line is a FAIL or an UNJUDGED \u2014 a question this could not ask is not a pass \u2014 and
98
189
  2 when the run could not be made at all. --strict promotes every WARN to a FAIL. --json prints the
99
190
  whole report for a CI step to read.`;
@@ -186,6 +277,15 @@ geonosis.json \u2192 "rails.egress"
186
277
  .geonosis/rails-run.json
187
278
  deniedEgress object[] what a run reached for and was refused. A GATE AT ZERO (D-025): one
188
279
  entry fails, and it is never a counter to hold flat`;
280
+ var sameFile = (fd, path) => {
281
+ try {
282
+ const open = fstatSync(fd);
283
+ const there = statSync(path);
284
+ return open.dev === there.dev && open.ino === there.ino;
285
+ } catch {
286
+ return false;
287
+ }
288
+ };
189
289
  var main = async () => {
190
290
  const argv = process.argv.slice(2);
191
291
  if (argv.includes("--help") || argv.includes("-h")) {
@@ -199,7 +299,21 @@ var main = async () => {
199
299
  return 0;
200
300
  }
201
301
  const { json, ...options } = parseDoctorArgs(argv, process.cwd());
302
+ const started = Date.now();
202
303
  const report = await runDoctor(options);
304
+ const at = envelopePath(options.root, "doctor");
305
+ if (sameFile(process.stdout.fd, at)) {
306
+ process.stderr.write(
307
+ `geonosis-doctor: stdout is ${ENVELOPES_DIR}/doctor.json, so no envelope was written \u2014 it would have overwritten the report redirected there. Send --json somewhere else and the envelope is written after every run.
308
+ `
309
+ );
310
+ } else {
311
+ writeEnvelope({
312
+ envelope: doctorEnvelope({ durationMs: Date.now() - started, report }),
313
+ next: DOCTOR_NEXT,
314
+ root: options.root
315
+ });
316
+ }
203
317
  process.stdout.write(json ? formatJson(report) : formatDoctor(report));
204
318
  return report.ok ? 0 : 1;
205
319
  };
package/dist/index.d.ts CHANGED
@@ -83,6 +83,8 @@ type DoctorOptions = {
83
83
  type DoctorReport = {
84
84
  counts: Record<Verdict, number>;
85
85
  findings: Finding[];
86
+ /** The checks this run asked. `--only` makes it a subset, and the envelope's denominator needs it. */
87
+ ran: CheckName[];
86
88
  /**
87
89
  * False as soon as one line is a FAIL or an UNJUDGED — after `--strict` has promoted the
88
90
  * warnings. "We could not tell" is not a pass; it is the quietest way there is of reading green.
@@ -189,7 +191,7 @@ declare const checkDrift: ({ readers, root, userSettings, workspaces, }: {
189
191
  * duplicated here on purpose: it is one comparison over three numbers, and it is the check.
190
192
  */
191
193
  declare const ENVELOPES_DIR = ".geonosis/envelopes";
192
- declare const NO_ENVELOPES = "no .geonosis/envelopes/*.json \u2014 the tools write one per run, so an absent envelope is a run nobody has made here yet, and it is not a balanced one";
194
+ declare const NO_ENVELOPES = "no .geonosis/envelopes/*.json \u2014 the tools write one per run, so an absent envelope is a run nobody has made here yet, and it is not a balanced one (this run\u2019s own doctor.json is not one of them: it is written after these checks, and balanced at write time)";
193
195
  declare const checkEnvelopes: ({ root }: {
194
196
  root: string;
195
197
  }) => Finding[];
package/dist/index.js CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  resolveFrom,
44
44
  runDoctor,
45
45
  satisfies
46
- } from "./chunk-SMCRXSH5.js";
46
+ } from "./chunk-R2R2OYH3.js";
47
47
  export {
48
48
  CHECKS,
49
49
  COMPOSITION_ROOT,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geonosis/doctor",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "types": "./dist/index.d.ts",
5
5
  "description": "The adoption doctor — declared ≠ loaded, enabled ≠ exercised, a baseline that grew, a runner whose exit code is the only verdict.",
6
6
  "keywords": [
@@ -35,7 +35,10 @@
35
35
  "dist"
36
36
  ],
37
37
  "dependencies": {
38
- "@geonosis/lint-parity": "2.0.0"
38
+ "@geonosis/lint-parity": "2.1.0"
39
+ },
40
+ "devDependencies": {
41
+ "@geonosis/ratchet": "2.1.0"
39
42
  },
40
43
  "peerDependencies": {
41
44
  "oxlint": ">=1.77"