@geonosis/doctor 2.10.0 → 2.11.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 +229 -0
- package/README.md +80 -38
- package/bin/geonosis-doctor.mjs +164 -17
- package/dist/{chunk-2ZVCJG5R.js → chunk-2VHAFVWH.js} +759 -489
- package/dist/doctor-cli.js +15 -8
- package/dist/index.d.ts +117 -33
- package/dist/index.js +15 -1
- package/package.json +4 -4
|
@@ -1,6 +1,91 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var CHECKS = [
|
|
3
|
+
"loaded",
|
|
4
|
+
"group",
|
|
5
|
+
"exercised",
|
|
6
|
+
"baseline",
|
|
7
|
+
"runner",
|
|
8
|
+
"envelope",
|
|
9
|
+
"drift",
|
|
10
|
+
"observability",
|
|
11
|
+
"deployed",
|
|
12
|
+
"rails",
|
|
13
|
+
"exams",
|
|
14
|
+
"seams",
|
|
15
|
+
"formatter"
|
|
16
|
+
];
|
|
17
|
+
var findingMaker = (check) => (subject, verdict, message) => ({
|
|
18
|
+
check,
|
|
19
|
+
message,
|
|
20
|
+
subject,
|
|
21
|
+
verdict
|
|
22
|
+
});
|
|
23
|
+
var DoctorError = class extends Error {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "DoctorError";
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var dependencyNamesOf = (manifest) => new Set(
|
|
30
|
+
[
|
|
31
|
+
manifest.dependencies,
|
|
32
|
+
manifest.devDependencies,
|
|
33
|
+
manifest.optionalDependencies,
|
|
34
|
+
manifest.peerDependencies
|
|
35
|
+
].flatMap((block) => Object.keys(block ?? {}))
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// src/geonosis-file.ts
|
|
39
|
+
import { existsSync, readFileSync } from "fs";
|
|
40
|
+
import { join } from "path";
|
|
41
|
+
var GEONOSIS_FILE = "geonosis.json";
|
|
42
|
+
var readGeonosisFile = (root) => {
|
|
43
|
+
const path = join(root, GEONOSIS_FILE);
|
|
44
|
+
if (!existsSync(path)) return { kind: "absent" };
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
47
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
48
|
+
return { error: "the top level is not an object", kind: "unreadable" };
|
|
49
|
+
}
|
|
50
|
+
return { config: parsed, kind: "present" };
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return { error: error.message, kind: "unreadable" };
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var unreadableGeonosis = (check, error) => findingMaker(check)(
|
|
56
|
+
GEONOSIS_FILE,
|
|
57
|
+
"FAIL",
|
|
58
|
+
`${GEONOSIS_FILE} could not be parsed: ${error}. A config nobody can read has not been read, and every question this check would answer is answered from a default nobody chose.`
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// src/apparatus.ts
|
|
62
|
+
import { realpathSync } from "fs";
|
|
63
|
+
import { join as join2, sep } from "path";
|
|
64
|
+
var APPARATUS_KEY = "apparatus";
|
|
65
|
+
var real = (path) => {
|
|
66
|
+
try {
|
|
67
|
+
return realpathSync(path);
|
|
68
|
+
} catch {
|
|
69
|
+
return path;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var holds = (parent, child) => child === parent || child.startsWith(`${parent}${sep}`);
|
|
73
|
+
var declaredApparatusOf = (root) => {
|
|
74
|
+
const read = readGeonosisFile(root);
|
|
75
|
+
if (read.kind !== "present") return [];
|
|
76
|
+
const doctor = read.config.doctor;
|
|
77
|
+
if (typeof doctor !== "object" || doctor === null) return [];
|
|
78
|
+
const declared = doctor[APPARATUS_KEY];
|
|
79
|
+
return Array.isArray(declared) ? declared.filter((one) => typeof one === "string" && one !== "").map((one) => join2(root, one)) : [];
|
|
80
|
+
};
|
|
81
|
+
var evidenceHolding = (config, evidence) => {
|
|
82
|
+
const dir = real(config.dir);
|
|
83
|
+
return evidence.find((one) => holds(real(one), dir));
|
|
84
|
+
};
|
|
85
|
+
|
|
1
86
|
// src/discover.ts
|
|
2
|
-
import { readdirSync, readFileSync, statSync } from "fs";
|
|
3
|
-
import { join, relative as relativeTo, sep } from "path";
|
|
87
|
+
import { readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
88
|
+
import { join as join3, relative as relativeTo, sep as sep2 } from "path";
|
|
4
89
|
var CONFIG_FILE = ".oxlintrc.json";
|
|
5
90
|
var MANIFEST_FILE = "package.json";
|
|
6
91
|
var RATCHET_FILE = "geonosis.ratchet.json";
|
|
@@ -12,7 +97,7 @@ var NEVER_WALKED = /* @__PURE__ */ new Set([
|
|
|
12
97
|
"storybook-static"
|
|
13
98
|
]);
|
|
14
99
|
var skipped = (name) => name.startsWith(".") || NEVER_WALKED.has(name);
|
|
15
|
-
var relativePath = (root, path) => relativeTo(root, path).split(
|
|
100
|
+
var relativePath = (root, path) => relativeTo(root, path).split(sep2).join("/");
|
|
16
101
|
var walk = (dir, onFile) => {
|
|
17
102
|
let entries;
|
|
18
103
|
try {
|
|
@@ -22,18 +107,18 @@ var walk = (dir, onFile) => {
|
|
|
22
107
|
}
|
|
23
108
|
for (const entry of entries) {
|
|
24
109
|
if (entry.isDirectory()) {
|
|
25
|
-
if (!skipped(entry.name)) walk(
|
|
110
|
+
if (!skipped(entry.name)) walk(join3(dir, entry.name), onFile);
|
|
26
111
|
continue;
|
|
27
112
|
}
|
|
28
|
-
if (entry.isFile()) onFile(
|
|
113
|
+
if (entry.isFile()) onFile(join3(dir, entry.name), entry.name);
|
|
29
114
|
}
|
|
30
115
|
};
|
|
31
|
-
var parse = (path) => JSON.parse(
|
|
116
|
+
var parse = (path) => JSON.parse(readFileSync2(path, "utf8"));
|
|
32
117
|
var stringsOf = (value) => Array.isArray(value) ? value.filter((one) => typeof one === "string") : [];
|
|
33
118
|
var rulesOf = (value) => typeof value === "object" && value !== null ? value : {};
|
|
34
119
|
var overridesOf = (value) => Array.isArray(value) ? value.filter((one) => typeof one === "object" && one !== null).map((one) => ({ files: stringsOf(one.files), rules: rulesOf(one.rules) })) : [];
|
|
35
120
|
var readConfig = (path, root) => {
|
|
36
|
-
const dir =
|
|
121
|
+
const dir = join3(path, "..");
|
|
37
122
|
const relative = relativePath(root, path);
|
|
38
123
|
try {
|
|
39
124
|
const config = parse(path);
|
|
@@ -62,7 +147,7 @@ var discoverConfigs = (root) => {
|
|
|
62
147
|
walk(root, (path, name) => {
|
|
63
148
|
if (name === CONFIG_FILE) found.push(readConfig(path, root));
|
|
64
149
|
});
|
|
65
|
-
return found.toSorted((a, b) => a.relative.localeCompare(b.relative));
|
|
150
|
+
return found.toSorted((a, b) => a.relative.localeCompare(b.relative, "en-US"));
|
|
66
151
|
};
|
|
67
152
|
var discoverWorkspaces = (root) => {
|
|
68
153
|
const found = [];
|
|
@@ -74,9 +159,9 @@ var discoverWorkspaces = (root) => {
|
|
|
74
159
|
} catch {
|
|
75
160
|
return;
|
|
76
161
|
}
|
|
77
|
-
found.push({ dir:
|
|
162
|
+
found.push({ dir: join3(path, ".."), manifest, relative: relativePath(root, join3(path, "..")) });
|
|
78
163
|
});
|
|
79
|
-
return found.toSorted((a, b) => a.relative.localeCompare(b.relative));
|
|
164
|
+
return found.toSorted((a, b) => a.relative.localeCompare(b.relative, "en-US"));
|
|
80
165
|
};
|
|
81
166
|
var TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
82
167
|
var testFilesUnder = (dir) => filesUnder(dir, (name) => TEST_FILE.test(name));
|
|
@@ -88,7 +173,7 @@ var filesUnder = (dir, match) => {
|
|
|
88
173
|
return found;
|
|
89
174
|
};
|
|
90
175
|
var readRatchet = (root) => {
|
|
91
|
-
const path =
|
|
176
|
+
const path = join3(root, RATCHET_FILE);
|
|
92
177
|
try {
|
|
93
178
|
statSync(path);
|
|
94
179
|
} catch {
|
|
@@ -101,40 +186,12 @@ var readRatchet = (root) => {
|
|
|
101
186
|
};
|
|
102
187
|
};
|
|
103
188
|
|
|
104
|
-
// src/types.ts
|
|
105
|
-
var CHECKS = [
|
|
106
|
-
"loaded",
|
|
107
|
-
"group",
|
|
108
|
-
"exercised",
|
|
109
|
-
"baseline",
|
|
110
|
-
"runner",
|
|
111
|
-
"envelope",
|
|
112
|
-
"drift",
|
|
113
|
-
"observability",
|
|
114
|
-
"deployed",
|
|
115
|
-
"rails",
|
|
116
|
-
"exams",
|
|
117
|
-
"seams"
|
|
118
|
-
];
|
|
119
|
-
var findingMaker = (check) => (subject, verdict, message) => ({
|
|
120
|
-
check,
|
|
121
|
-
message,
|
|
122
|
-
subject,
|
|
123
|
-
verdict
|
|
124
|
-
});
|
|
125
|
-
var DoctorError = class extends Error {
|
|
126
|
-
constructor(message) {
|
|
127
|
-
super(message);
|
|
128
|
-
this.name = "DoctorError";
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
|
|
132
189
|
// src/baseline.ts
|
|
133
190
|
import { spawnSync } from "child_process";
|
|
134
191
|
var DEFAULT_REF = "origin/main";
|
|
135
192
|
var git = (root, args) => {
|
|
136
193
|
const run = spawnSync("git", args, { cwd: root, encoding: "utf8" });
|
|
137
|
-
return { code: run.status ?? -1,
|
|
194
|
+
return { code: run.status ?? -1, stdout: run.stdout ?? "" };
|
|
138
195
|
};
|
|
139
196
|
var defaultRef = (root) => git(root, ["rev-parse", "--verify", "--quiet", DEFAULT_REF]).code === 0 ? DEFAULT_REF : void 0;
|
|
140
197
|
var numbersAt = (root, ref, path) => {
|
|
@@ -142,7 +199,7 @@ var numbersAt = (root, ref, path) => {
|
|
|
142
199
|
if (shown.code !== 0) return void 0;
|
|
143
200
|
let parsed;
|
|
144
201
|
try {
|
|
145
|
-
parsed = JSON.parse(shown.
|
|
202
|
+
parsed = JSON.parse(shown.stdout);
|
|
146
203
|
} catch {
|
|
147
204
|
return void 0;
|
|
148
205
|
}
|
|
@@ -196,8 +253,8 @@ var checkBaseline = ({ ref, root }) => {
|
|
|
196
253
|
};
|
|
197
254
|
|
|
198
255
|
// src/deployed.ts
|
|
199
|
-
import { existsSync, readFileSync as
|
|
200
|
-
import { join as
|
|
256
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
257
|
+
import { join as join4 } from "path";
|
|
201
258
|
var DEPLOYED_FILE = ".geonosis/deployed.json";
|
|
202
259
|
var NOT_WRITTEN = "no .geonosis/deployed.json \u2014 it is written by the pipeline after promote, and its absence is not a pass";
|
|
203
260
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -283,19 +340,11 @@ var differences = (kind, declared, deployed) => {
|
|
|
283
340
|
...extra.length === 0 ? [] : [`deployed but not declared ${kind}: ${extra.join(", ")}`]
|
|
284
341
|
];
|
|
285
342
|
};
|
|
286
|
-
var releaseOf = (root) => {
|
|
287
|
-
const path = join2(root, "geonosis.json");
|
|
288
|
-
if (!existsSync(path)) return void 0;
|
|
289
|
-
try {
|
|
290
|
-
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
291
|
-
return isRecord(parsed) && isRecord(parsed["release"]) ? parsed["release"] : {};
|
|
292
|
-
} catch {
|
|
293
|
-
return {};
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
343
|
var checkDeployed = ({ root }) => {
|
|
297
|
-
const
|
|
298
|
-
if (
|
|
344
|
+
const read = readGeonosisFile(root);
|
|
345
|
+
if (read.kind === "absent") return [];
|
|
346
|
+
if (read.kind === "unreadable") return [unreadableGeonosis("deployed", read.error)];
|
|
347
|
+
const release = isRecord(read.config["release"]) ? read.config["release"] : {};
|
|
299
348
|
const configs = listOf(release["wrangler"]);
|
|
300
349
|
const secrets = listOf(release["secrets"]);
|
|
301
350
|
if (configs.length === 0 && secrets.length === 0) {
|
|
@@ -312,7 +361,7 @@ var checkDeployed = ({ root }) => {
|
|
|
312
361
|
for (const relative of configs) {
|
|
313
362
|
let parsed;
|
|
314
363
|
try {
|
|
315
|
-
parsed = parseJsonc(
|
|
364
|
+
parsed = parseJsonc(readFileSync3(join4(root, relative), "utf8"));
|
|
316
365
|
} catch (error) {
|
|
317
366
|
return [finding2("FAIL", relative, `could not be read: ${error.message}`)];
|
|
318
367
|
}
|
|
@@ -324,11 +373,11 @@ var checkDeployed = ({ root }) => {
|
|
|
324
373
|
declared.crons.push(...one.crons);
|
|
325
374
|
declared.routes.push(...one.routes);
|
|
326
375
|
}
|
|
327
|
-
const path =
|
|
328
|
-
if (!
|
|
376
|
+
const path = join4(root, DEPLOYED_FILE);
|
|
377
|
+
if (!existsSync2(path)) return [finding2("SKIP", DEPLOYED_FILE, NOT_WRITTEN)];
|
|
329
378
|
let reported;
|
|
330
379
|
try {
|
|
331
|
-
reported = JSON.parse(
|
|
380
|
+
reported = JSON.parse(readFileSync3(path, "utf8"));
|
|
332
381
|
} catch (error) {
|
|
333
382
|
return [finding2("FAIL", DEPLOYED_FILE, `is not readable JSON: ${error.message}`)];
|
|
334
383
|
}
|
|
@@ -349,26 +398,10 @@ var checkDeployed = ({ root }) => {
|
|
|
349
398
|
];
|
|
350
399
|
};
|
|
351
400
|
|
|
352
|
-
// src/repo-corpus.ts
|
|
353
|
-
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
354
|
-
import { join as join3 } from "path";
|
|
355
|
-
var GEONOSIS_FILE = "geonosis.json";
|
|
356
|
-
var repoCorpusOf = (root) => {
|
|
357
|
-
const path = join3(root, GEONOSIS_FILE);
|
|
358
|
-
if (!existsSync2(path)) return void 0;
|
|
359
|
-
try {
|
|
360
|
-
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
361
|
-
const declared = parsed.doctor?.corpus;
|
|
362
|
-
return typeof declared === "string" && declared !== "" ? join3(root, declared) : void 0;
|
|
363
|
-
} catch {
|
|
364
|
-
return void 0;
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
|
|
368
401
|
// src/resolve.ts
|
|
369
|
-
import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync } from "fs";
|
|
402
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync as realpathSync2 } from "fs";
|
|
370
403
|
import { createRequire } from "module";
|
|
371
|
-
import { dirname, join as
|
|
404
|
+
import { dirname, join as join5 } from "path";
|
|
372
405
|
import { pathToFileURL } from "url";
|
|
373
406
|
var packageNameOf = (specifier) => {
|
|
374
407
|
const parts2 = specifier.split("/");
|
|
@@ -386,8 +419,8 @@ var aboveRootMessage = (specifier, foundAt) => `${specifier} is not installed he
|
|
|
386
419
|
var aboveRoot = (root, name) => {
|
|
387
420
|
let at = dirname(root);
|
|
388
421
|
for (; ; ) {
|
|
389
|
-
const dir =
|
|
390
|
-
if (existsSync3(
|
|
422
|
+
const dir = join5(at, "node_modules", name);
|
|
423
|
+
if (existsSync3(join5(dir, "package.json"))) return dir;
|
|
391
424
|
const parent = dirname(at);
|
|
392
425
|
if (parent === at) return void 0;
|
|
393
426
|
at = parent;
|
|
@@ -395,14 +428,14 @@ var aboveRoot = (root, name) => {
|
|
|
395
428
|
};
|
|
396
429
|
var resolveFrom = (dir, specifier, root) => {
|
|
397
430
|
if (specifier.startsWith(".") || specifier.startsWith("/")) {
|
|
398
|
-
return createRequire(
|
|
431
|
+
return createRequire(join5(dir, "noop.js")).resolve(specifier);
|
|
399
432
|
}
|
|
400
433
|
const name = packageNameOf(specifier);
|
|
401
434
|
let at = dir;
|
|
402
435
|
let reachedRoot = false;
|
|
403
436
|
for (; ; ) {
|
|
404
|
-
if (existsSync3(
|
|
405
|
-
return createRequire(
|
|
437
|
+
if (existsSync3(join5(at, "node_modules", name, "package.json"))) {
|
|
438
|
+
return createRequire(join5(at, "noop.js")).resolve(specifier);
|
|
406
439
|
}
|
|
407
440
|
if (at === root) {
|
|
408
441
|
reachedRoot = true;
|
|
@@ -424,7 +457,7 @@ var resolveFrom = (dir, specifier, root) => {
|
|
|
424
457
|
var packageDirOf = (entry, name) => {
|
|
425
458
|
let dir = dirname(entry);
|
|
426
459
|
for (; ; ) {
|
|
427
|
-
const manifest =
|
|
460
|
+
const manifest = join5(dir, "package.json");
|
|
428
461
|
if (existsSync3(manifest)) {
|
|
429
462
|
try {
|
|
430
463
|
const parsed = JSON.parse(readFileSync4(manifest, "utf8"));
|
|
@@ -500,23 +533,92 @@ var presumptionsOf = async (entry) => {
|
|
|
500
533
|
)
|
|
501
534
|
};
|
|
502
535
|
};
|
|
503
|
-
var corpusOfPlugin = (from, specifier, root) =>
|
|
504
|
-
var
|
|
536
|
+
var corpusOfPlugin = (from, specifier, root) => join5(packageDirOf(resolveFrom(from, specifier, root), specifier), "corpus");
|
|
537
|
+
var real2 = (path) => {
|
|
505
538
|
try {
|
|
506
|
-
return
|
|
539
|
+
return realpathSync2(path);
|
|
507
540
|
} catch {
|
|
508
541
|
return path;
|
|
509
542
|
}
|
|
510
543
|
};
|
|
511
|
-
var relativeToRoot = (root, path) => relativePath(
|
|
544
|
+
var relativeToRoot = (root, path) => relativePath(real2(root), real2(path));
|
|
512
545
|
|
|
513
546
|
// src/group.ts
|
|
514
|
-
import {
|
|
515
|
-
import { join as
|
|
547
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
548
|
+
import { join as join8 } from "path";
|
|
516
549
|
|
|
517
|
-
// src/
|
|
550
|
+
// src/callers.ts
|
|
518
551
|
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
|
|
519
|
-
import { join as
|
|
552
|
+
import { join as join6, resolve } from "path";
|
|
553
|
+
var WORKFLOWS = ".github/workflows";
|
|
554
|
+
var A_WORKFLOW = (name) => name.endsWith(".yml") || name.endsWith(".yaml");
|
|
555
|
+
var readOr = (path) => {
|
|
556
|
+
try {
|
|
557
|
+
return readFileSync5(path, "utf8");
|
|
558
|
+
} catch {
|
|
559
|
+
return "";
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
var workflowSources = (root) => {
|
|
563
|
+
const dir = join6(root, WORKFLOWS);
|
|
564
|
+
if (!existsSync4(dir)) return [];
|
|
565
|
+
return readdirSync2(dir).filter(A_WORKFLOW).map((name) => readOr(join6(dir, name)));
|
|
566
|
+
};
|
|
567
|
+
var commandsOf = (step) => {
|
|
568
|
+
if (typeof step === "string") return [step];
|
|
569
|
+
if (typeof step !== "object" || step === null) return [];
|
|
570
|
+
const shape = step;
|
|
571
|
+
if (typeof shape.command === "string") return [shape.command];
|
|
572
|
+
if (Array.isArray(shape.parallel)) return shape.parallel.flatMap(commandsOf);
|
|
573
|
+
return [];
|
|
574
|
+
};
|
|
575
|
+
var declaredTiers = (root) => {
|
|
576
|
+
const read = readGeonosisFile(root);
|
|
577
|
+
if (read.kind !== "present") return { commands: [], names: [] };
|
|
578
|
+
const verify = read.config.verify;
|
|
579
|
+
if (typeof verify !== "object" || verify === null || Array.isArray(verify)) {
|
|
580
|
+
return { commands: [], names: [] };
|
|
581
|
+
}
|
|
582
|
+
const tiers = verify;
|
|
583
|
+
return {
|
|
584
|
+
commands: Object.values(tiers).flatMap(
|
|
585
|
+
(steps) => Array.isArray(steps) ? steps.flatMap(commandsOf) : []
|
|
586
|
+
),
|
|
587
|
+
names: Object.keys(tiers)
|
|
588
|
+
};
|
|
589
|
+
};
|
|
590
|
+
var everythingRun = (root, workspaces) => {
|
|
591
|
+
const tiers = declaredTiers(root);
|
|
592
|
+
return {
|
|
593
|
+
run: [
|
|
594
|
+
...tiers.commands,
|
|
595
|
+
...workspaces.flatMap((one) => Object.values(one.manifest.scripts ?? {})),
|
|
596
|
+
...(readRatchet(root)?.counters ?? []).map((entry) => String(entry.command ?? "")),
|
|
597
|
+
...workflowSources(root)
|
|
598
|
+
].join("\n"),
|
|
599
|
+
tiers: tiers.names
|
|
600
|
+
};
|
|
601
|
+
};
|
|
602
|
+
var A_SCRIPT_FILE = /\.(?:[cm]?[jt]sx?|sh|bash)$/;
|
|
603
|
+
var scriptFilesNamedBy = (root, lines) => {
|
|
604
|
+
const seen = /* @__PURE__ */ new Set();
|
|
605
|
+
const found = [];
|
|
606
|
+
for (const line of lines) {
|
|
607
|
+
for (const token of line.split(/[\s'"`]+/)) {
|
|
608
|
+
if (!A_SCRIPT_FILE.test(token) || seen.has(token)) continue;
|
|
609
|
+
seen.add(token);
|
|
610
|
+
const path = resolve(root, token);
|
|
611
|
+
if (!path.startsWith(root) || !existsSync4(path)) continue;
|
|
612
|
+
const source = readOr(path);
|
|
613
|
+
if (source !== "") found.push({ at: token, source });
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return found;
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
// src/hooks.ts
|
|
620
|
+
import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
|
|
621
|
+
import { join as join7 } from "path";
|
|
520
622
|
var HOOK_FILES = ["lefthook.yml", "lefthook.yaml", ".lefthook.yml", ".lefthook.yaml"];
|
|
521
623
|
var HOOK_DIRS = [".husky", ".githooks"];
|
|
522
624
|
var VERSION_MANAGED = /(?:^|[\s;&|(])(?<runner>pnpm|npx|bunx|yarn)\b(?:\s+(?:exec|run|dlx|x))?\s+(?<bin>geonosis(?:-[a-z-]+)?)\b/;
|
|
@@ -525,20 +627,20 @@ var hookLines = (root) => {
|
|
|
525
627
|
const found = [];
|
|
526
628
|
const read = (path) => {
|
|
527
629
|
const at = relativePath(root, path);
|
|
528
|
-
for (const line of
|
|
630
|
+
for (const line of readFileSync6(path, "utf8").split("\n")) {
|
|
529
631
|
if (line.trim() !== "") found.push({ at, line });
|
|
530
632
|
}
|
|
531
633
|
};
|
|
532
634
|
for (const name of HOOK_FILES) {
|
|
533
|
-
const path =
|
|
534
|
-
if (
|
|
635
|
+
const path = join7(root, name);
|
|
636
|
+
if (existsSync5(path)) read(path);
|
|
535
637
|
}
|
|
536
638
|
for (const name of HOOK_DIRS) {
|
|
537
|
-
const dir =
|
|
538
|
-
if (!
|
|
539
|
-
for (const entry of
|
|
639
|
+
const dir = join7(root, name);
|
|
640
|
+
if (!existsSync5(dir)) continue;
|
|
641
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
540
642
|
if (entry.isFile() && !entry.name.startsWith("_") && !entry.name.startsWith(".")) {
|
|
541
|
-
read(
|
|
643
|
+
read(join7(dir, entry.name));
|
|
542
644
|
}
|
|
543
645
|
}
|
|
544
646
|
}
|
|
@@ -571,16 +673,8 @@ var FIXED_GROUP = [
|
|
|
571
673
|
"geonosis"
|
|
572
674
|
];
|
|
573
675
|
var KIT_GROUP = { name: "@geonosis fixed group", packages: FIXED_GROUP };
|
|
574
|
-
var
|
|
575
|
-
const
|
|
576
|
-
if (!existsSync5(path)) return void 0;
|
|
577
|
-
let parsed;
|
|
578
|
-
try {
|
|
579
|
-
parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
580
|
-
} catch {
|
|
581
|
-
return void 0;
|
|
582
|
-
}
|
|
583
|
-
const declared = parsed.doctor?.groups;
|
|
676
|
+
var groupsFrom = (config) => {
|
|
677
|
+
const declared = config.doctor?.groups;
|
|
584
678
|
if (declared === void 0) return void 0;
|
|
585
679
|
if (!Array.isArray(declared) || declared.length === 0) {
|
|
586
680
|
throw new DoctorError(
|
|
@@ -597,6 +691,13 @@ var declaredGroupsOf = (root) => {
|
|
|
597
691
|
return { name: group.name, packages: group.packages };
|
|
598
692
|
});
|
|
599
693
|
};
|
|
694
|
+
var declaredGroupsOf = (root) => {
|
|
695
|
+
const read = readGeonosisFile(root);
|
|
696
|
+
if (read.kind === "unreadable") {
|
|
697
|
+
throw new DoctorError(`${GEONOSIS_FILE} could not be parsed: ${read.error}`);
|
|
698
|
+
}
|
|
699
|
+
return read.kind === "present" ? groupsFrom(read.config) : void 0;
|
|
700
|
+
};
|
|
600
701
|
var labelOf = (workspace) => workspace.relative === "" ? "root" : workspace.relative;
|
|
601
702
|
var versionOf = (dir, name, root) => {
|
|
602
703
|
let entry;
|
|
@@ -607,7 +708,7 @@ var versionOf = (dir, name, root) => {
|
|
|
607
708
|
}
|
|
608
709
|
try {
|
|
609
710
|
const manifest = JSON.parse(
|
|
610
|
-
|
|
711
|
+
readFileSync7(join8(packageDirOf(entry, name), "package.json"), "utf8")
|
|
611
712
|
);
|
|
612
713
|
return typeof manifest.version === "string" ? { kind: "read", version: manifest.version } : { kind: "unreadable", why: "its package.json declares no version" };
|
|
613
714
|
} catch (error) {
|
|
@@ -639,7 +740,7 @@ var DOORS = ["@geonosis/cli", "geonosis"];
|
|
|
639
740
|
var manifestOf = (root, name) => {
|
|
640
741
|
try {
|
|
641
742
|
return JSON.parse(
|
|
642
|
-
|
|
743
|
+
readFileSync7(join8(packageDirOf(resolveFrom(root, name, root), name), "package.json"), "utf8")
|
|
643
744
|
);
|
|
644
745
|
} catch {
|
|
645
746
|
return void 0;
|
|
@@ -649,17 +750,8 @@ var binNamesOf = (manifest, name) => {
|
|
|
649
750
|
const declared = manifest?.bin;
|
|
650
751
|
return typeof declared === "string" ? [name.replace(/^@[^/]+\//, "")] : Object.keys(declared ?? {});
|
|
651
752
|
};
|
|
652
|
-
var declaredIn2 = (manifest) => [
|
|
653
|
-
|
|
654
|
-
[
|
|
655
|
-
manifest.dependencies,
|
|
656
|
-
manifest.devDependencies,
|
|
657
|
-
manifest.optionalDependencies,
|
|
658
|
-
manifest.peerDependencies
|
|
659
|
-
].flatMap((block) => Object.keys(block ?? {}))
|
|
660
|
-
)
|
|
661
|
-
];
|
|
662
|
-
var calls = (lines, bin) => new RegExp(`(?:^|[\\s;&|(/])${bin}(?:\\s|$)`).test(lines);
|
|
753
|
+
var declaredIn2 = (manifest) => [...dependencyNamesOf(manifest)];
|
|
754
|
+
var calls = (lines, bin) => new RegExp(`(?:^|[\\s;&|(/'"\`])${bin}(?:[\\s;&|)'"\`,\\]]|$)`).test(lines);
|
|
663
755
|
var workspaceCaller = (workspaces, bins) => {
|
|
664
756
|
for (const workspace of workspaces) {
|
|
665
757
|
if (workspace.relative === "") continue;
|
|
@@ -681,20 +773,25 @@ var rootDeclarations = (root, workspaces) => {
|
|
|
681
773
|
return manifest === void 0 ? [] : Object.keys(manifest.dependencies ?? {});
|
|
682
774
|
})
|
|
683
775
|
);
|
|
684
|
-
const
|
|
776
|
+
const lines = [
|
|
685
777
|
...Object.values(here.manifest.scripts ?? {}),
|
|
686
|
-
...hookLines(root).map((one) => one.line)
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
);
|
|
778
|
+
...hookLines(root).map((one) => one.line),
|
|
779
|
+
...workflowSources(root),
|
|
780
|
+
...declaredTiers(root).commands
|
|
781
|
+
];
|
|
782
|
+
const callers = [...lines, ...scriptFilesNamedBy(root, lines).map((one) => one.source)].join("\n");
|
|
783
|
+
const loadedHere = new Set(readConfig(join8(root, CONFIG_FILE), root).jsPlugins);
|
|
784
|
+
const spare = declared.filter((name) => {
|
|
785
|
+
if (DOORS.includes(name) || !owned.has(name) || loadedHere.has(name)) return false;
|
|
786
|
+
const bins = binNamesOf(manifestOf(root, name), name);
|
|
787
|
+
return bins.length > 0 && !bins.some((bin) => calls(callers, bin));
|
|
788
|
+
});
|
|
692
789
|
if (spare.length === 0) {
|
|
693
790
|
return [
|
|
694
791
|
finding3(
|
|
695
792
|
"package.json",
|
|
696
793
|
"OK",
|
|
697
|
-
`every @geonosis package this root declares is ${door} or a bin
|
|
794
|
+
`every @geonosis package this root declares is ${door} or a bin something here calls by name \u2014 a script, a hook, a script file they run, a workflow or a tier`
|
|
698
795
|
)
|
|
699
796
|
];
|
|
700
797
|
}
|
|
@@ -711,7 +808,7 @@ var rootDeclarations = (root, workspaces) => {
|
|
|
711
808
|
return finding3(
|
|
712
809
|
name,
|
|
713
810
|
"WARN",
|
|
714
|
-
`the root declares it and
|
|
811
|
+
`the root declares it and nothing in this repo calls its bin \u2014 no root or workspace script, no committed hook, no script file those name, no CI workflow, no geonosis.json tier. ${door} already brings it, so this second declaration is a version that can disagree with the door's, and every tool that asks what is used here reads it as unused. Drop it from the root, or declare it in the workspace whose code imports it \u2014 and if something this check cannot see does call the bin, keep the declaration: under an isolated linker it is what links the bin into node_modules/.bin`
|
|
715
812
|
);
|
|
716
813
|
});
|
|
717
814
|
};
|
|
@@ -721,8 +818,15 @@ var checkGroup = ({
|
|
|
721
818
|
workspaces
|
|
722
819
|
}) => {
|
|
723
820
|
const findings = [...rootDeclarations(root, workspaces)];
|
|
724
|
-
|
|
821
|
+
let resolved = groups;
|
|
822
|
+
if (resolved === void 0) {
|
|
823
|
+
const read = readGeonosisFile(root);
|
|
824
|
+
if (read.kind === "unreadable") return [...findings, unreadableGeonosis("group", read.error)];
|
|
825
|
+
resolved = read.kind === "present" ? declaredGroupsOf(root) : void 0;
|
|
826
|
+
}
|
|
827
|
+
for (const group of resolved ?? [KIT_GROUP]) {
|
|
725
828
|
const found = [];
|
|
829
|
+
let halfway = false;
|
|
726
830
|
for (const workspace of workspaces) {
|
|
727
831
|
const at = labelOf(workspace);
|
|
728
832
|
const here = [];
|
|
@@ -749,7 +853,10 @@ var checkGroup = ({
|
|
|
749
853
|
}
|
|
750
854
|
}
|
|
751
855
|
const split2 = here.length > 0 ? perWorkspace(group, at, here) : void 0;
|
|
752
|
-
if (split2 !== void 0)
|
|
856
|
+
if (split2 !== void 0) {
|
|
857
|
+
findings.push(split2);
|
|
858
|
+
halfway = true;
|
|
859
|
+
}
|
|
753
860
|
found.push(...here);
|
|
754
861
|
}
|
|
755
862
|
if (found.length === 0) {
|
|
@@ -771,7 +878,7 @@ var checkGroup = ({
|
|
|
771
878
|
return line === void 0 ? [] : [line];
|
|
772
879
|
});
|
|
773
880
|
findings.push(...split);
|
|
774
|
-
if (split.length === 0) {
|
|
881
|
+
if (split.length === 0 && !halfway) {
|
|
775
882
|
const versions = [...new Set(found.map((one) => one.version))];
|
|
776
883
|
findings.push(
|
|
777
884
|
finding3(
|
|
@@ -786,14 +893,15 @@ var checkGroup = ({
|
|
|
786
893
|
};
|
|
787
894
|
|
|
788
895
|
// src/drift.ts
|
|
789
|
-
import { closeSync, existsSync as existsSync6, openSync, readdirSync as
|
|
896
|
+
import { closeSync, existsSync as existsSync6, openSync, readdirSync as readdirSync4, readFileSync as readFileSync8, readSync } from "fs";
|
|
790
897
|
import { homedir } from "os";
|
|
791
|
-
import { join as
|
|
898
|
+
import { join as join9, sep as sep3 } from "path";
|
|
792
899
|
|
|
793
900
|
// src/overrides.ts
|
|
794
901
|
var SPECIAL = /* @__PURE__ */ new Set(["$", "(", ")", "+", ".", "/", "@", "\\", "^", "|"]);
|
|
795
902
|
var globToRegExp = (glob) => {
|
|
796
903
|
let source = "";
|
|
904
|
+
let braces = 0;
|
|
797
905
|
for (let at = 0; at < glob.length; at += 1) {
|
|
798
906
|
const character = glob[at] ?? "";
|
|
799
907
|
if (character === "*") {
|
|
@@ -816,13 +924,15 @@ var globToRegExp = (glob) => {
|
|
|
816
924
|
}
|
|
817
925
|
if (character === "{") {
|
|
818
926
|
source += "(?:";
|
|
927
|
+
braces += 1;
|
|
819
928
|
continue;
|
|
820
929
|
}
|
|
821
|
-
if (character === "}") {
|
|
930
|
+
if (character === "}" && braces > 0) {
|
|
822
931
|
source += ")";
|
|
932
|
+
braces -= 1;
|
|
823
933
|
continue;
|
|
824
934
|
}
|
|
825
|
-
if (character === ",") {
|
|
935
|
+
if (character === "," && braces > 0) {
|
|
826
936
|
source += "|";
|
|
827
937
|
continue;
|
|
828
938
|
}
|
|
@@ -854,29 +964,27 @@ var governing = (layers, path) => {
|
|
|
854
964
|
};
|
|
855
965
|
|
|
856
966
|
// src/drift.ts
|
|
857
|
-
var WORKFLOWS = ".github/workflows";
|
|
858
967
|
var SETTINGS = ".claude/settings.json";
|
|
859
|
-
var GEONOSIS = "geonosis.json";
|
|
860
968
|
var LAW = "CLAUDE.md";
|
|
861
969
|
var CEILING = 200;
|
|
862
970
|
var SWITCHED_OFF = /^\s*if:\s*(?:\$\{\{\s*)?false\b/m;
|
|
863
971
|
var finding4 = findingMaker("drift");
|
|
864
972
|
var ci = (root) => {
|
|
865
|
-
const dir =
|
|
973
|
+
const dir = join9(root, WORKFLOWS);
|
|
866
974
|
if (!existsSync6(dir)) {
|
|
867
975
|
return [finding4(WORKFLOWS, "SKIP", "there are no workflows here to read")];
|
|
868
976
|
}
|
|
869
977
|
return filesUnder(dir, (name) => name.endsWith(".yml") || name.endsWith(".yaml")).map((path) => {
|
|
870
978
|
const at = relativePath(root, path);
|
|
871
|
-
return SWITCHED_OFF.test(
|
|
979
|
+
return SWITCHED_OFF.test(readFileSync8(path, "utf8")) ? finding4(
|
|
872
980
|
at,
|
|
873
981
|
"FAIL",
|
|
874
982
|
"a job or step here is switched off by a condition that can never be true \u2014 every gate downstream of it reports green having run nothing"
|
|
875
983
|
) : finding4(at, "OK", "nothing in it is switched off");
|
|
876
984
|
});
|
|
877
985
|
};
|
|
878
|
-
var
|
|
879
|
-
var ownersOf = (path, workspaces) => workspaces.filter((one) =>
|
|
986
|
+
var holds2 = (parent, child) => child === parent || child.startsWith(`${parent}${sep3}`);
|
|
987
|
+
var ownersOf = (path, workspaces) => workspaces.filter((one) => holds2(one.dir, path)).toSorted((a, b) => b.dir.length - a.dir.length);
|
|
880
988
|
var orphanTests = (root, workspaces) => {
|
|
881
989
|
const orphaned = /* @__PURE__ */ new Map();
|
|
882
990
|
for (const path of testFilesUnder(root)) {
|
|
@@ -916,7 +1024,7 @@ var pathsRunByScript = (script) => script.split(BETWEEN_COMMANDS).map(pathRunBy)
|
|
|
916
1024
|
var scriptPaths = (workspaces) => {
|
|
917
1025
|
const missing = workspaces.flatMap(
|
|
918
1026
|
(one) => Object.entries(one.manifest.scripts ?? {}).flatMap(
|
|
919
|
-
([name, script]) => pathsRunByScript(script).filter((path) => !existsSync6(
|
|
1027
|
+
([name, script]) => pathsRunByScript(script).filter((path) => !existsSync6(join9(one.dir, path))).map((path) => ({ name, path, workspace: one }))
|
|
920
1028
|
)
|
|
921
1029
|
);
|
|
922
1030
|
if (missing.length === 0) {
|
|
@@ -952,7 +1060,9 @@ var linkedBins = (root, workspaces) => {
|
|
|
952
1060
|
}
|
|
953
1061
|
const missing = workspaces.flatMap(
|
|
954
1062
|
(one) => Object.entries(one.manifest.scripts ?? {}).flatMap(
|
|
955
|
-
([script, body]) => [...new Set(body.split(/[\s;|&()]+/).filter((word) => bins.has(word)))].filter(
|
|
1063
|
+
([script, body]) => [...new Set(body.split(/[\s;|&()]+/).filter((word) => bins.has(word)))].filter(
|
|
1064
|
+
(name) => !existsSync6(join9(one.dir, "node_modules/.bin", name)) && !existsSync6(join9(root, "node_modules/.bin", name))
|
|
1065
|
+
).map((name) => ({ name, script, workspace: one }))
|
|
956
1066
|
)
|
|
957
1067
|
);
|
|
958
1068
|
if (missing.length === 0) {
|
|
@@ -1018,21 +1128,6 @@ var markerIn = (path) => {
|
|
|
1018
1128
|
closeSync(handle);
|
|
1019
1129
|
}
|
|
1020
1130
|
};
|
|
1021
|
-
var everythingRun = (root, workspaces) => {
|
|
1022
|
-
const scripts = workspaces.flatMap((one) => Object.values(one.manifest.scripts ?? {}));
|
|
1023
|
-
const counters = (readRatchet(root)?.counters ?? []).map((entry) => String(entry.command ?? ""));
|
|
1024
|
-
const workflows = filesUnder(
|
|
1025
|
-
join7(root, WORKFLOWS),
|
|
1026
|
-
(name) => name.endsWith(".yml") || name.endsWith(".yaml")
|
|
1027
|
-
).map((path) => {
|
|
1028
|
-
try {
|
|
1029
|
-
return readFileSync7(path, "utf8");
|
|
1030
|
-
} catch {
|
|
1031
|
-
return "";
|
|
1032
|
-
}
|
|
1033
|
-
});
|
|
1034
|
-
return [...scripts, ...counters, ...workflows].join("\n");
|
|
1035
|
-
};
|
|
1036
1131
|
var generatedFiles = (root, workspaces) => {
|
|
1037
1132
|
const marked = filesUnder(root, (name) => CAN_CARRY_A_MARKER.test(name)).map((path) => ({ marker: markerIn(path), path })).filter((one) => one.marker !== void 0);
|
|
1038
1133
|
if (marked.length === 0) {
|
|
@@ -1044,7 +1139,7 @@ var generatedFiles = (root, workspaces) => {
|
|
|
1044
1139
|
)
|
|
1045
1140
|
];
|
|
1046
1141
|
}
|
|
1047
|
-
const run = everythingRun(root, workspaces);
|
|
1142
|
+
const { run } = everythingRun(root, workspaces);
|
|
1048
1143
|
return marked.map(({ marker, path }) => {
|
|
1049
1144
|
const at = relativePath(root, path);
|
|
1050
1145
|
if ("writes" in marker) {
|
|
@@ -1064,27 +1159,18 @@ var generatedFiles = (root, workspaces) => {
|
|
|
1064
1159
|
return run.includes(marker.gate) ? finding4(at, "OK", `generated, and \`${marker.gate}\` reads it back`) : finding4(
|
|
1065
1160
|
at,
|
|
1066
1161
|
"FAIL",
|
|
1067
|
-
`generated, and nothing here runs \`${marker.gate}\` \u2014 the gate it names. A written file no check reads is the write half of an instrument with no read half: it can drift from its source for ever and every gate stays green. Add \`${marker.gate}\` to a script, a counter or a workflow`
|
|
1162
|
+
`generated, and nothing here runs \`${marker.gate}\` \u2014 the gate it names. A written file no check reads is the write half of an instrument with no read half: it can drift from its source for ever and every gate stays green. Add \`${marker.gate}\` to a script, a verify tier, a counter or a workflow`
|
|
1068
1163
|
);
|
|
1069
1164
|
});
|
|
1070
1165
|
};
|
|
1071
|
-
var readGeonosis = (root) => {
|
|
1072
|
-
const path = join7(root, GEONOSIS);
|
|
1073
|
-
if (!existsSync6(path)) return void 0;
|
|
1074
|
-
try {
|
|
1075
|
-
return JSON.parse(readFileSync7(path, "utf8"));
|
|
1076
|
-
} catch {
|
|
1077
|
-
return void 0;
|
|
1078
|
-
}
|
|
1079
|
-
};
|
|
1080
1166
|
var law = (root, config) => {
|
|
1081
1167
|
const declared = config?.law ?? {};
|
|
1082
1168
|
const file = typeof declared.file === "string" ? declared.file : LAW;
|
|
1083
|
-
const path =
|
|
1169
|
+
const path = join9(root, file);
|
|
1084
1170
|
if (!existsSync6(path)) {
|
|
1085
1171
|
return [finding4(file, "SKIP", "there is no law file here to measure")];
|
|
1086
1172
|
}
|
|
1087
|
-
const source =
|
|
1173
|
+
const source = readFileSync8(path, "utf8");
|
|
1088
1174
|
const lines = source.split("\n").length - (source.endsWith("\n") ? 1 : 0);
|
|
1089
1175
|
if (typeof declared.maxLines !== "number") {
|
|
1090
1176
|
return [
|
|
@@ -1108,7 +1194,7 @@ var KIT_PLUGIN = "geonosis";
|
|
|
1108
1194
|
var enablesKit = (path) => {
|
|
1109
1195
|
if (!existsSync6(path)) return false;
|
|
1110
1196
|
try {
|
|
1111
|
-
const parsed = JSON.parse(
|
|
1197
|
+
const parsed = JSON.parse(readFileSync8(path, "utf8"));
|
|
1112
1198
|
const enabled = parsed.enabledPlugins;
|
|
1113
1199
|
if (typeof enabled !== "object" || enabled === null) return false;
|
|
1114
1200
|
return Object.entries(enabled).some(
|
|
@@ -1119,7 +1205,7 @@ var enablesKit = (path) => {
|
|
|
1119
1205
|
}
|
|
1120
1206
|
};
|
|
1121
1207
|
var hooks = (root, userSettings) => {
|
|
1122
|
-
const path =
|
|
1208
|
+
const path = join9(root, SETTINGS);
|
|
1123
1209
|
if (enablesKit(path)) {
|
|
1124
1210
|
return [finding4(SETTINGS, "OK", `\`enabledPlugins\` here enables the kit\u2019s plugin`)];
|
|
1125
1211
|
}
|
|
@@ -1142,9 +1228,9 @@ var hooks = (root, userSettings) => {
|
|
|
1142
1228
|
};
|
|
1143
1229
|
var ALLOW_BUILDS_KEY = "allowBuilds";
|
|
1144
1230
|
var allowedBuilds = (root) => {
|
|
1145
|
-
const path =
|
|
1231
|
+
const path = join9(root, WORKSPACE_YAML);
|
|
1146
1232
|
if (!existsSync6(path)) return void 0;
|
|
1147
|
-
const lines =
|
|
1233
|
+
const lines = readFileSync8(path, "utf8").split("\n");
|
|
1148
1234
|
const at = lines.findIndex((line) => new RegExp(`^${ALLOW_BUILDS_KEY}\\s*:`).test(line));
|
|
1149
1235
|
if (at < 0) return void 0;
|
|
1150
1236
|
const names = [];
|
|
@@ -1158,7 +1244,10 @@ var allowedBuilds = (root) => {
|
|
|
1158
1244
|
return names;
|
|
1159
1245
|
};
|
|
1160
1246
|
var PLATFORM = /(?:^|[-/])(?:darwin|linux|win32|windows|freebsd|openbsd|android|sunos)(?:[-.]|$)|(?:^|[-/])(?:arm64|x64|ia32|ppc64|s390x|riscv64)(?:[-.]|$)/;
|
|
1161
|
-
var familyOf = (name) =>
|
|
1247
|
+
var familyOf = (name) => {
|
|
1248
|
+
const at = name.search(PLATFORM);
|
|
1249
|
+
return at > 0 ? name.slice(0, at) : name;
|
|
1250
|
+
};
|
|
1162
1251
|
var platformSplitBuilds = (root) => {
|
|
1163
1252
|
const names = allowedBuilds(root);
|
|
1164
1253
|
if (names === void 0 || names.length === 0) return [];
|
|
@@ -1195,7 +1284,7 @@ var pathsLintedBy = (script, dir) => {
|
|
|
1195
1284
|
if (word.startsWith("-")) continue;
|
|
1196
1285
|
const before = words[at - 1] ?? "";
|
|
1197
1286
|
if (TAKES_A_VALUE.has(before)) continue;
|
|
1198
|
-
if (existsSync6(
|
|
1287
|
+
if (existsSync6(join9(dir, word))) named2.push(word.replace(/^\.\//, "").replace(/\/$/, ""));
|
|
1199
1288
|
}
|
|
1200
1289
|
return named2;
|
|
1201
1290
|
};
|
|
@@ -1203,12 +1292,12 @@ var LINTABLE = /\.[cm]?[jt]sx?$/;
|
|
|
1203
1292
|
var sourceDirsIn = (dir) => {
|
|
1204
1293
|
let entries;
|
|
1205
1294
|
try {
|
|
1206
|
-
entries =
|
|
1295
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
1207
1296
|
} catch {
|
|
1208
1297
|
return [];
|
|
1209
1298
|
}
|
|
1210
1299
|
return entries.filter(
|
|
1211
|
-
(entry) => entry.isDirectory() && !entry.name.startsWith(".") && !NEVER_WALKED.has(entry.name) && filesUnder(
|
|
1300
|
+
(entry) => entry.isDirectory() && !entry.name.startsWith(".") && !NEVER_WALKED.has(entry.name) && filesUnder(join9(dir, entry.name), (name) => LINTABLE.test(name)).length > 0
|
|
1212
1301
|
).map((entry) => entry.name);
|
|
1213
1302
|
};
|
|
1214
1303
|
var lintScopes = (root, workspaces) => {
|
|
@@ -1248,7 +1337,6 @@ var OBSERVABILITY = "@geonosis/observability";
|
|
|
1248
1337
|
var COMPOSITION_ROOT = "src/platform.ts";
|
|
1249
1338
|
var FLOOR_PACKAGES = [
|
|
1250
1339
|
"@geonosis/assistant",
|
|
1251
|
-
"@geonosis/buketi-ui",
|
|
1252
1340
|
"@geonosis/conformance",
|
|
1253
1341
|
"@geonosis/db",
|
|
1254
1342
|
"@geonosis/document",
|
|
@@ -1262,6 +1350,7 @@ var FLOOR_PACKAGES = [
|
|
|
1262
1350
|
"@geonosis/plans",
|
|
1263
1351
|
"@geonosis/recurring",
|
|
1264
1352
|
"@geonosis/search",
|
|
1353
|
+
"@geonosis/storefront-ui",
|
|
1265
1354
|
"@geonosis/ui",
|
|
1266
1355
|
"@geonosis/workflows"
|
|
1267
1356
|
];
|
|
@@ -1298,7 +1387,7 @@ var composedHere = (root, workspaces) => {
|
|
|
1298
1387
|
for (const path of filesUnder(root, (name) => SOURCE_FILE.test(name))) {
|
|
1299
1388
|
let body;
|
|
1300
1389
|
try {
|
|
1301
|
-
body =
|
|
1390
|
+
body = readFileSync8(path, "utf8");
|
|
1302
1391
|
} catch {
|
|
1303
1392
|
continue;
|
|
1304
1393
|
}
|
|
@@ -1333,8 +1422,8 @@ var doorBringing = (root, name, declared) => DOORS.filter((door) => declared.has
|
|
|
1333
1422
|
var dependenciesOf = (root, name) => {
|
|
1334
1423
|
try {
|
|
1335
1424
|
return JSON.parse(
|
|
1336
|
-
|
|
1337
|
-
|
|
1425
|
+
readFileSync8(
|
|
1426
|
+
join9(packageDirOf(resolveFrom(root, name, root), name), "package.json"),
|
|
1338
1427
|
"utf8"
|
|
1339
1428
|
)
|
|
1340
1429
|
).dependencies ?? {};
|
|
@@ -1350,21 +1439,12 @@ var resolves = (root, name) => {
|
|
|
1350
1439
|
return false;
|
|
1351
1440
|
}
|
|
1352
1441
|
};
|
|
1353
|
-
var declaredAnywhere = (workspaces) => new Set(
|
|
1354
|
-
workspaces.flatMap(
|
|
1355
|
-
(one) => [
|
|
1356
|
-
one.manifest.dependencies,
|
|
1357
|
-
one.manifest.devDependencies,
|
|
1358
|
-
one.manifest.optionalDependencies,
|
|
1359
|
-
one.manifest.peerDependencies
|
|
1360
|
-
].flatMap((field) => Object.keys(field ?? {}))
|
|
1361
|
-
)
|
|
1362
|
-
);
|
|
1442
|
+
var declaredAnywhere = (workspaces) => new Set(workspaces.flatMap((one) => [...dependencyNamesOf(one.manifest)]));
|
|
1363
1443
|
var blocks = (root, config, readers, workspaces) => {
|
|
1364
1444
|
if (config === void 0) {
|
|
1365
1445
|
return [
|
|
1366
1446
|
finding4(
|
|
1367
|
-
|
|
1447
|
+
GEONOSIS_FILE,
|
|
1368
1448
|
"SKIP",
|
|
1369
1449
|
"there is no geonosis.json here, so no block says what this repo asks the kit to do"
|
|
1370
1450
|
)
|
|
@@ -1437,7 +1517,7 @@ var pluginDirsOf = (level) => {
|
|
|
1437
1517
|
return { manifests: options.manifests ?? ["index.ts"], registry, roots: options.roots };
|
|
1438
1518
|
};
|
|
1439
1519
|
var pluginDirLayers = (root) => {
|
|
1440
|
-
const path =
|
|
1520
|
+
const path = join9(root, ".oxlintrc.json");
|
|
1441
1521
|
if (!existsSync6(path)) return [];
|
|
1442
1522
|
const layers = [];
|
|
1443
1523
|
for (const layer of layersOf(readConfig(path, root), PLUGIN_DIR_RULE)) {
|
|
@@ -1447,8 +1527,8 @@ var pluginDirLayers = (root) => {
|
|
|
1447
1527
|
return layers;
|
|
1448
1528
|
};
|
|
1449
1529
|
var layerOver = (layers, root, relative) => {
|
|
1450
|
-
const at =
|
|
1451
|
-
const inside =
|
|
1530
|
+
const at = join9(root, relative);
|
|
1531
|
+
const inside = readdirSync4(at, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => `${relative}/${entry.name}`);
|
|
1452
1532
|
const paths = inside.length === 0 ? [relative] : inside;
|
|
1453
1533
|
const wrapped = layers.map((layer) => ({ files: layer.files, level: layer.dirs }));
|
|
1454
1534
|
for (const path of paths) {
|
|
@@ -1469,7 +1549,7 @@ var pluginDirs = (root) => {
|
|
|
1469
1549
|
];
|
|
1470
1550
|
}
|
|
1471
1551
|
const missing = [...new Set(layers.flatMap((layer) => layer.dirs.registry))].filter(
|
|
1472
|
-
(registry) => !existsSync6(
|
|
1552
|
+
(registry) => !existsSync6(join9(root, registry))
|
|
1473
1553
|
);
|
|
1474
1554
|
if (missing.length > 0) {
|
|
1475
1555
|
return missing.map(
|
|
@@ -1479,17 +1559,17 @@ var pluginDirs = (root) => {
|
|
|
1479
1559
|
const source = /* @__PURE__ */ new Map();
|
|
1480
1560
|
const unreachable = /* @__PURE__ */ new Map();
|
|
1481
1561
|
for (const rootDir of new Set(layers.flatMap((layer) => layer.dirs.roots))) {
|
|
1482
|
-
const at =
|
|
1562
|
+
const at = join9(root, rootDir);
|
|
1483
1563
|
if (!existsSync6(at)) continue;
|
|
1484
|
-
for (const entry of
|
|
1564
|
+
for (const entry of readdirSync4(at, { withFileTypes: true })) {
|
|
1485
1565
|
if (!entry.isDirectory()) continue;
|
|
1486
1566
|
const relative = `${rootDir}/${entry.name}`;
|
|
1487
1567
|
const governs = layerOver(layers, root, relative);
|
|
1488
1568
|
if (governs === void 0) continue;
|
|
1489
1569
|
const key = governs.registry.join(", ");
|
|
1490
|
-
const registry = source.get(key) ?? governs.registry.map((half) =>
|
|
1570
|
+
const registry = source.get(key) ?? governs.registry.map((half) => readFileSync8(join9(root, half), "utf8")).join("\n");
|
|
1491
1571
|
source.set(key, registry);
|
|
1492
|
-
const hasManifest = governs.manifests.some((name) => existsSync6(
|
|
1572
|
+
const hasManifest = governs.manifests.some((name) => existsSync6(join9(at, entry.name, name)));
|
|
1493
1573
|
if (!hasManifest || registry.includes(entry.name)) continue;
|
|
1494
1574
|
unreachable.set(key, [...unreachable.get(key) ?? [], relative]);
|
|
1495
1575
|
}
|
|
@@ -1505,11 +1585,57 @@ var pluginDirs = (root) => {
|
|
|
1505
1585
|
};
|
|
1506
1586
|
var WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
1507
1587
|
var HOIST_KEY = "publicHoistPattern";
|
|
1588
|
+
var pnpmMajorOf = (manifest) => {
|
|
1589
|
+
const packageManager = manifest["packageManager"];
|
|
1590
|
+
const pinned = typeof packageManager === "string" ? /^pnpm@(\d+)[\d.]*/.exec(packageManager) : null;
|
|
1591
|
+
if (pinned?.[1] !== void 0) return { major: Number(pinned[1]), said: packageManager };
|
|
1592
|
+
const engine = manifest["engines"]?.["pnpm"];
|
|
1593
|
+
const ranged = typeof engine === "string" ? /(\d+)/.exec(engine) : null;
|
|
1594
|
+
if (ranged?.[1] !== void 0) return { major: Number(ranged[1]), said: `engines.pnpm ${engine}` };
|
|
1595
|
+
return void 0;
|
|
1596
|
+
};
|
|
1597
|
+
var pnpmBlock = (root) => {
|
|
1598
|
+
let manifest;
|
|
1599
|
+
try {
|
|
1600
|
+
manifest = JSON.parse(readFileSync8(join9(root, "package.json"), "utf8"));
|
|
1601
|
+
} catch {
|
|
1602
|
+
return [];
|
|
1603
|
+
}
|
|
1604
|
+
const block = manifest["pnpm"];
|
|
1605
|
+
if (typeof block !== "object" || block === null || Array.isArray(block)) return [];
|
|
1606
|
+
const keys = Object.keys(block).map((key) => `pnpm.${key}`);
|
|
1607
|
+
if (keys.length === 0) return [];
|
|
1608
|
+
const pinned = pnpmMajorOf(manifest);
|
|
1609
|
+
const named2 = keys.join(", ");
|
|
1610
|
+
const move = `Move each key to ${WORKSPACE_YAML} (\`overrides:\` at the top level), then ${HOIST_REPAIR} \u2014 a plain install answers "Already up to date" over the old tree.`;
|
|
1611
|
+
if (pinned === void 0) {
|
|
1612
|
+
return [
|
|
1613
|
+
finding4(
|
|
1614
|
+
"package.json",
|
|
1615
|
+
"WARN",
|
|
1616
|
+
`it has a pnpm block (${named2}) and nothing here pins pnpm \u2014 pnpm \u226511 does not read that block at all, so whether these settings apply depends on whichever pnpm is on the machine. ${move}`
|
|
1617
|
+
)
|
|
1618
|
+
];
|
|
1619
|
+
}
|
|
1620
|
+
return pinned.major >= 11 ? [
|
|
1621
|
+
finding4(
|
|
1622
|
+
"package.json",
|
|
1623
|
+
"FAIL",
|
|
1624
|
+
`it has a pnpm block (${named2}) that pnpm \u226511 does not read, and this repo pins ${pinned.said}: every key in it is silently ignored, and the install still says "Already up to date". ${move}`
|
|
1625
|
+
)
|
|
1626
|
+
] : [
|
|
1627
|
+
finding4(
|
|
1628
|
+
"package.json",
|
|
1629
|
+
"OK",
|
|
1630
|
+
`its pnpm block (${named2}) is read by the ${pinned.said} pinned here; pnpm 11 stops reading it, so move it to ${WORKSPACE_YAML} before that bump`
|
|
1631
|
+
)
|
|
1632
|
+
];
|
|
1633
|
+
};
|
|
1508
1634
|
var HOIST_REPAIR = "rm -rf node_modules/.modules.yaml node_modules/.pnpm-workspace-state-v1.json && pnpm install";
|
|
1509
1635
|
var hoistPatterns = (root) => {
|
|
1510
|
-
const path =
|
|
1636
|
+
const path = join9(root, WORKSPACE_YAML);
|
|
1511
1637
|
if (!existsSync6(path)) return void 0;
|
|
1512
|
-
const lines =
|
|
1638
|
+
const lines = readFileSync8(path, "utf8").split("\n");
|
|
1513
1639
|
const at = lines.findIndex((line) => new RegExp(`^${HOIST_KEY}\\s*:`).test(line));
|
|
1514
1640
|
if (at < 0) return void 0;
|
|
1515
1641
|
const patterns = [];
|
|
@@ -1546,7 +1672,7 @@ var publicHoists = (root, workspaces) => {
|
|
|
1546
1672
|
`this ${HOIST_KEY} matches no workspace package here, so what it should have linked at the root is a question this cannot answer`
|
|
1547
1673
|
);
|
|
1548
1674
|
}
|
|
1549
|
-
const pruned = hoisted.filter((name) => !existsSync6(
|
|
1675
|
+
const pruned = hoisted.filter((name) => !existsSync6(join9(root, "node_modules", name)));
|
|
1550
1676
|
if (pruned.length === 0) {
|
|
1551
1677
|
return finding4(
|
|
1552
1678
|
pattern,
|
|
@@ -1564,11 +1690,13 @@ var publicHoists = (root, workspaces) => {
|
|
|
1564
1690
|
var checkDrift = ({
|
|
1565
1691
|
readers = READERS,
|
|
1566
1692
|
root,
|
|
1567
|
-
userSettings =
|
|
1693
|
+
userSettings = join9(homedir(), SETTINGS),
|
|
1568
1694
|
workspaces
|
|
1569
1695
|
}) => {
|
|
1570
|
-
const
|
|
1696
|
+
const read = readGeonosisFile(root);
|
|
1697
|
+
const config = read.kind === "present" ? read.config : void 0;
|
|
1571
1698
|
return [
|
|
1699
|
+
...read.kind === "unreadable" ? [unreadableGeonosis("drift", read.error)] : [],
|
|
1572
1700
|
...ci(root),
|
|
1573
1701
|
...orphanTests(root, workspaces),
|
|
1574
1702
|
...scriptPaths(workspaces),
|
|
@@ -1577,15 +1705,53 @@ var checkDrift = ({
|
|
|
1577
1705
|
...generatedFiles(root, workspaces),
|
|
1578
1706
|
...pluginDirs(root),
|
|
1579
1707
|
...publicHoists(root, workspaces),
|
|
1708
|
+
...pnpmBlock(root),
|
|
1580
1709
|
...composedHere(root, workspaces),
|
|
1581
1710
|
...lintScopes(root, workspaces),
|
|
1582
1711
|
...platformSplitBuilds(root),
|
|
1583
1712
|
...law(root, config),
|
|
1584
1713
|
...hooks(root, userSettings),
|
|
1585
|
-
...blocks(root, config, readers, workspaces)
|
|
1714
|
+
...read.kind === "unreadable" ? [] : blocks(root, config, readers, workspaces)
|
|
1586
1715
|
];
|
|
1587
1716
|
};
|
|
1588
1717
|
|
|
1718
|
+
// src/missing-option.ts
|
|
1719
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1720
|
+
var HEADER = /requires option `([^`]+)`/;
|
|
1721
|
+
var missingOption = (said2) => {
|
|
1722
|
+
const named2 = said2.option;
|
|
1723
|
+
if (typeof named2 === "string") return named2;
|
|
1724
|
+
const message = said2.message ?? said2.message;
|
|
1725
|
+
return HEADER.exec(String(message))?.[1];
|
|
1726
|
+
};
|
|
1727
|
+
var PROGRAM = {
|
|
1728
|
+
body: [],
|
|
1729
|
+
loc: { end: { column: 0, line: 1 }, start: { column: 0, line: 1 } },
|
|
1730
|
+
range: [0, 0],
|
|
1731
|
+
type: "Program"
|
|
1732
|
+
};
|
|
1733
|
+
var missingOptionOf = (rule, options) => {
|
|
1734
|
+
const reported = [];
|
|
1735
|
+
let visitor;
|
|
1736
|
+
try {
|
|
1737
|
+
visitor = rule.create({ options, report: (said2) => reported.push(said2) });
|
|
1738
|
+
} catch (error) {
|
|
1739
|
+
return missingOption(error);
|
|
1740
|
+
}
|
|
1741
|
+
const program = visitor?.Program;
|
|
1742
|
+
if (typeof program !== "function") return void 0;
|
|
1743
|
+
try {
|
|
1744
|
+
program(PROGRAM);
|
|
1745
|
+
} catch {
|
|
1746
|
+
return void 0;
|
|
1747
|
+
}
|
|
1748
|
+
return reported.map(missingOption).find((option) => option !== void 0);
|
|
1749
|
+
};
|
|
1750
|
+
var constructibleRulesOf = async (entry) => {
|
|
1751
|
+
const loaded = await import(pathToFileURL2(entry).href).catch(() => void 0);
|
|
1752
|
+
return loaded?.default?.rules;
|
|
1753
|
+
};
|
|
1754
|
+
|
|
1589
1755
|
// src/exercised.ts
|
|
1590
1756
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1591
1757
|
import {
|
|
@@ -1593,12 +1759,12 @@ import {
|
|
|
1593
1759
|
existsSync as existsSync7,
|
|
1594
1760
|
mkdirSync,
|
|
1595
1761
|
mkdtempSync,
|
|
1596
|
-
readFileSync as
|
|
1762
|
+
readFileSync as readFileSync9,
|
|
1597
1763
|
rmSync,
|
|
1598
1764
|
writeFileSync
|
|
1599
1765
|
} from "fs";
|
|
1600
1766
|
import { tmpdir } from "os";
|
|
1601
|
-
import { dirname as dirname2, join as
|
|
1767
|
+
import { dirname as dirname2, join as join10, resolve as resolve2 } from "path";
|
|
1602
1768
|
import { corpusOf, readManifest } from "@geonosis/lint-parity";
|
|
1603
1769
|
|
|
1604
1770
|
// src/rule-level.ts
|
|
@@ -1615,9 +1781,9 @@ var refusal = (error) => {
|
|
|
1615
1781
|
return said2.length > LIMIT ? `${said2.slice(0, LIMIT)}\u2026` : said2;
|
|
1616
1782
|
};
|
|
1617
1783
|
var reasonFrom = (config, oxlint) => {
|
|
1618
|
-
const dir = mkdtempSync(
|
|
1784
|
+
const dir = mkdtempSync(join10(tmpdir(), "geonosis-doctor-why-"));
|
|
1619
1785
|
try {
|
|
1620
|
-
const probe =
|
|
1786
|
+
const probe = join10(dir, "probe.tsx");
|
|
1621
1787
|
writeFileSync(probe, "export const probe = 1\n");
|
|
1622
1788
|
const run = spawnSync2(
|
|
1623
1789
|
oxlint,
|
|
@@ -1679,6 +1845,18 @@ var enablingLayersOf = (config, rule) => {
|
|
|
1679
1845
|
return layers;
|
|
1680
1846
|
};
|
|
1681
1847
|
var under = (config, rule) => `${rule} under ${optionSetsOf(config, rule).map((options) => JSON.stringify(options)).join(" / ")}`;
|
|
1848
|
+
var unconfiguredOf = async (config, entry, enabled) => {
|
|
1849
|
+
const rules = await constructibleRulesOf(entry);
|
|
1850
|
+
if (rules === void 0) return [];
|
|
1851
|
+
const found = [];
|
|
1852
|
+
for (const id of enabled) {
|
|
1853
|
+
const rule = rules[id.slice(id.indexOf("/") + 1)];
|
|
1854
|
+
if (rule === void 0) continue;
|
|
1855
|
+
const option = enablingLayersOf(config, id).map((layer) => missingOptionOf(rule, [...layer.options])).find((one) => one !== void 0);
|
|
1856
|
+
if (option !== void 0) found.push([id, option]);
|
|
1857
|
+
}
|
|
1858
|
+
return found;
|
|
1859
|
+
};
|
|
1682
1860
|
var enabledHere = (config, plugin) => [
|
|
1683
1861
|
.../* @__PURE__ */ new Set([
|
|
1684
1862
|
...enabledRulesOf(config.rules, plugin),
|
|
@@ -1709,8 +1887,8 @@ var throughProbes = ({
|
|
|
1709
1887
|
}) => {
|
|
1710
1888
|
const refused = /* @__PURE__ */ new Map();
|
|
1711
1889
|
const placed = /* @__PURE__ */ new Set();
|
|
1712
|
-
const dir = mkdtempSync(
|
|
1713
|
-
const here =
|
|
1890
|
+
const dir = mkdtempSync(join10(tmpdir(), "geonosis-doctor-probe-"));
|
|
1891
|
+
const here = join10(dir, "corpus");
|
|
1714
1892
|
try {
|
|
1715
1893
|
cpSync(corpus, here, { recursive: true });
|
|
1716
1894
|
for (const rule of silent) {
|
|
@@ -1747,7 +1925,7 @@ var throughProbes = ({
|
|
|
1747
1925
|
mounted = rehomed;
|
|
1748
1926
|
}
|
|
1749
1927
|
for (const file of mounted) {
|
|
1750
|
-
const at2 =
|
|
1928
|
+
const at2 = join10(here, file.path);
|
|
1751
1929
|
if (placed.has(at2)) continue;
|
|
1752
1930
|
placed.add(at2);
|
|
1753
1931
|
mkdirSync(dirname2(at2), { recursive: true });
|
|
@@ -1760,26 +1938,32 @@ var throughProbes = ({
|
|
|
1760
1938
|
refused.set(rule, String(error.message));
|
|
1761
1939
|
}
|
|
1762
1940
|
}
|
|
1763
|
-
const probeConfig = JSON.parse(
|
|
1941
|
+
const probeConfig = JSON.parse(readFileSync9(config.path, "utf8"));
|
|
1764
1942
|
const configDir = dirname2(config.path);
|
|
1765
1943
|
probeConfig.jsPlugins = (probeConfig.jsPlugins ?? []).map(
|
|
1766
|
-
(spec) => spec.startsWith(".") || spec.startsWith("/") ?
|
|
1944
|
+
(spec) => spec.startsWith(".") || spec.startsWith("/") ? resolve2(configDir, spec) : packageDirOf(resolveFrom(configDir, spec, root), spec)
|
|
1767
1945
|
);
|
|
1768
|
-
const at =
|
|
1946
|
+
const at = join10(here, ".oxlintrc-geonosis-probe.json");
|
|
1769
1947
|
writeFileSync(at, JSON.stringify(probeConfig, null, 2));
|
|
1770
1948
|
const run = spawnSync2(
|
|
1771
1949
|
oxlint,
|
|
1772
1950
|
["-c", at, "--format", "json", "--no-ignore", "--disable-nested-config", here],
|
|
1773
1951
|
{ cwd: dir, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }
|
|
1774
1952
|
);
|
|
1775
|
-
|
|
1953
|
+
if (run.error !== void 0) throw new Error(`oxlint could not start: ${run.error.message}`);
|
|
1954
|
+
const said2 = `${run.stdout ?? ""}${run.stderr ?? ""}`.trim().slice(0, LIMIT);
|
|
1955
|
+
let parsed;
|
|
1776
1956
|
try {
|
|
1777
|
-
|
|
1778
|
-
for (const one of parsed.diagnostics ?? []) {
|
|
1779
|
-
const renamed = /^([\w@./-]+)\((.+)\)$/.exec(one.code ?? "");
|
|
1780
|
-
if (renamed !== null) fired.add(`${renamed[1]}/${renamed[2]}`);
|
|
1781
|
-
}
|
|
1957
|
+
parsed = JSON.parse(run.stdout);
|
|
1782
1958
|
} catch {
|
|
1959
|
+
throw new Error(
|
|
1960
|
+
`oxlint exited ${run.status ?? -1} over the probes, printing ${said2 || "nothing"}`
|
|
1961
|
+
);
|
|
1962
|
+
}
|
|
1963
|
+
const fired = /* @__PURE__ */ new Set();
|
|
1964
|
+
for (const one of parsed.diagnostics ?? []) {
|
|
1965
|
+
const renamed = /^([\w@./-]+)\((.+)\)$/.exec(one.code ?? "");
|
|
1966
|
+
if (renamed !== null) fired.add(`${renamed[1]}/${renamed[2]}`);
|
|
1783
1967
|
}
|
|
1784
1968
|
return { fired, refused };
|
|
1785
1969
|
} finally {
|
|
@@ -1826,6 +2010,16 @@ var checkExercised = async ({
|
|
|
1826
2010
|
if (enabled.length === 0) {
|
|
1827
2011
|
return retired.length === 0 ? said2("SKIP", `no ${manifest.plugin} rule is enabled here`) : said2("WARN", `nothing enabled here is a live ${manifest.plugin} rule`);
|
|
1828
2012
|
}
|
|
2013
|
+
const unconfigured = entry === void 0 ? [] : await unconfiguredOf(config, entry, enabled);
|
|
2014
|
+
if (unconfigured.length > 0) {
|
|
2015
|
+
return said2(
|
|
2016
|
+
"FAIL",
|
|
2017
|
+
`${countOf(
|
|
2018
|
+
unconfigured.map(([rule]) => rule),
|
|
2019
|
+
"judge"
|
|
2020
|
+
)} nothing \u2014 enabled without the option each requires, so its only finding is the refusal, which is not reach: ${unconfigured.map(([rule, option]) => `${rule} (\`${option}\`)`).join(", ")}`
|
|
2021
|
+
);
|
|
2022
|
+
}
|
|
1829
2023
|
let reach;
|
|
1830
2024
|
try {
|
|
1831
2025
|
reach = corpusOf({ configA: config.path, configB: config.path, corpus, oxlint }).reach;
|
|
@@ -1904,8 +2098,8 @@ var checkExercised = async ({
|
|
|
1904
2098
|
};
|
|
1905
2099
|
|
|
1906
2100
|
// src/envelope.ts
|
|
1907
|
-
import { existsSync as existsSync8, readdirSync as
|
|
1908
|
-
import { join as
|
|
2101
|
+
import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync10 } from "fs";
|
|
2102
|
+
import { join as join11 } from "path";
|
|
1909
2103
|
var ENVELOPES_DIR = ".geonosis/envelopes";
|
|
1910
2104
|
var THIS_TOOL = "doctor";
|
|
1911
2105
|
var OWN = `${THIS_TOOL}.json`;
|
|
@@ -1963,14 +2157,14 @@ var judge = (subject, name, parsed) => {
|
|
|
1963
2157
|
);
|
|
1964
2158
|
};
|
|
1965
2159
|
var checkEnvelopes = ({ root }) => {
|
|
1966
|
-
const dir =
|
|
1967
|
-
const files = existsSync8(dir) ?
|
|
2160
|
+
const dir = join11(root, ENVELOPES_DIR);
|
|
2161
|
+
const files = existsSync8(dir) ? readdirSync5(dir).filter((name) => name.endsWith(".json") && name !== OWN).toSorted() : [];
|
|
1968
2162
|
if (files.length === 0) return [finding6("SKIP", ENVELOPES_DIR, NO_ENVELOPES)];
|
|
1969
2163
|
return files.map((name) => {
|
|
1970
2164
|
const subject = `${ENVELOPES_DIR}/${name}`;
|
|
1971
2165
|
let parsed;
|
|
1972
2166
|
try {
|
|
1973
|
-
parsed = JSON.parse(
|
|
2167
|
+
parsed = JSON.parse(readFileSync10(join11(dir, name), "utf8"));
|
|
1974
2168
|
} catch (error) {
|
|
1975
2169
|
return finding6("FAIL", subject, `is not readable JSON: ${error.message}. ${NEXT}`);
|
|
1976
2170
|
}
|
|
@@ -1979,8 +2173,8 @@ var checkEnvelopes = ({ root }) => {
|
|
|
1979
2173
|
};
|
|
1980
2174
|
|
|
1981
2175
|
// src/exams.ts
|
|
1982
|
-
import { existsSync as existsSync9, readFileSync as
|
|
1983
|
-
import { dirname as dirname3, join as
|
|
2176
|
+
import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
|
|
2177
|
+
import { dirname as dirname3, join as join12 } from "path";
|
|
1984
2178
|
var SCOPE = "@geonosis/";
|
|
1985
2179
|
var EXAM = /(?:^|[^$\w])([$\w]*Conformance)\b/g;
|
|
1986
2180
|
var finding7 = findingMaker("exams");
|
|
@@ -2001,12 +2195,12 @@ var declaredBy = (workspaces) => {
|
|
|
2001
2195
|
}
|
|
2002
2196
|
}
|
|
2003
2197
|
}
|
|
2004
|
-
return [...found].map(([name, from]) => ({ from, name })).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
2198
|
+
return [...found].map(([name, from]) => ({ from, name })).toSorted((a, b) => a.name.localeCompare(b.name, "en-US"));
|
|
2005
2199
|
};
|
|
2006
2200
|
var declarationsOf = (dir) => {
|
|
2007
|
-
const at =
|
|
2201
|
+
const at = join12(dir, "package.json");
|
|
2008
2202
|
if (!existsSync9(at)) return "";
|
|
2009
|
-
const manifest = JSON.parse(
|
|
2203
|
+
const manifest = JSON.parse(readFileSync11(at, "utf8"));
|
|
2010
2204
|
const entries = [];
|
|
2011
2205
|
const walk2 = (value) => {
|
|
2012
2206
|
if (typeof value === "string" && value.endsWith(".d.ts")) entries.push(value);
|
|
@@ -2017,7 +2211,7 @@ var declarationsOf = (dir) => {
|
|
|
2017
2211
|
walk2({ exports: manifest["exports"], types: manifest["types"] });
|
|
2018
2212
|
return [...new Set(entries)].map((entry) => {
|
|
2019
2213
|
try {
|
|
2020
|
-
return
|
|
2214
|
+
return readFileSync11(join12(dir, entry), "utf8");
|
|
2021
2215
|
} catch {
|
|
2022
2216
|
return "";
|
|
2023
2217
|
}
|
|
@@ -2026,8 +2220,8 @@ var declarationsOf = (dir) => {
|
|
|
2026
2220
|
var installedAt = (from, name, root) => {
|
|
2027
2221
|
let at = from;
|
|
2028
2222
|
for (; ; ) {
|
|
2029
|
-
const dir =
|
|
2030
|
-
if (existsSync9(
|
|
2223
|
+
const dir = join12(at, "node_modules", name);
|
|
2224
|
+
if (existsSync9(join12(dir, "package.json"))) return dir;
|
|
2031
2225
|
if (at === root) return void 0;
|
|
2032
2226
|
const up = dirname3(at);
|
|
2033
2227
|
if (up === at) return void 0;
|
|
@@ -2043,7 +2237,7 @@ var checkExams = ({
|
|
|
2043
2237
|
root,
|
|
2044
2238
|
workspaces
|
|
2045
2239
|
}) => {
|
|
2046
|
-
const tests = testFilesUnder(root).map((path) => ({ path, source:
|
|
2240
|
+
const tests = testFilesUnder(root).map((path) => ({ path, source: readFileSync11(path, "utf8") }));
|
|
2047
2241
|
const found = [];
|
|
2048
2242
|
for (const { from, name } of declaredBy(workspaces)) {
|
|
2049
2243
|
const exams = examsShippedBy(from, name, root);
|
|
@@ -2069,65 +2263,125 @@ var checkExams = ({
|
|
|
2069
2263
|
};
|
|
2070
2264
|
|
|
2071
2265
|
// src/claude-plugin.ts
|
|
2072
|
-
import { existsSync as existsSync10, readFileSync as
|
|
2266
|
+
import { existsSync as existsSync10, readFileSync as readFileSync12, realpathSync as realpathSync3 } from "fs";
|
|
2073
2267
|
import { homedir as homedir2 } from "os";
|
|
2074
|
-
import { join as
|
|
2268
|
+
import { join as join13, resolve as resolve3 } from "path";
|
|
2075
2269
|
var INSTALLED_PLUGINS = ".claude/plugins/installed_plugins.json";
|
|
2076
2270
|
var PLUGIN_NAME = "geonosis";
|
|
2077
|
-
var
|
|
2078
|
-
|
|
2271
|
+
var USER_SCOPE = "user";
|
|
2272
|
+
var installedPlugins = (home, name) => {
|
|
2273
|
+
const at = join13(home, INSTALLED_PLUGINS);
|
|
2079
2274
|
if (!existsSync10(at)) return [];
|
|
2080
2275
|
let record;
|
|
2081
2276
|
try {
|
|
2082
|
-
record = JSON.parse(
|
|
2277
|
+
record = JSON.parse(readFileSync12(at, "utf8"));
|
|
2083
2278
|
} catch {
|
|
2084
2279
|
return [];
|
|
2085
2280
|
}
|
|
2086
|
-
return Object.entries(record.plugins ?? {}).filter(([key]) => (key.split("@")[0] ?? key) === name).flatMap(([, value]) => Array.isArray(value) ? value : []).flatMap(
|
|
2281
|
+
return Object.entries(record.plugins ?? {}).filter(([key]) => (key.split("@")[0] ?? key) === name).flatMap(([, value]) => Array.isArray(value) ? value : []).flatMap(
|
|
2282
|
+
(one) => typeof one.version === "string" ? [
|
|
2283
|
+
{
|
|
2284
|
+
...typeof one.projectPath === "string" ? { projectPath: one.projectPath } : {},
|
|
2285
|
+
...typeof one.scope === "string" ? { scope: one.scope } : {},
|
|
2286
|
+
version: one.version
|
|
2287
|
+
}
|
|
2288
|
+
] : []
|
|
2289
|
+
);
|
|
2290
|
+
};
|
|
2291
|
+
var installedPluginVersions = (home, name) => installedPlugins(home, name).map((one) => one.version);
|
|
2292
|
+
var real3 = (path) => {
|
|
2293
|
+
try {
|
|
2294
|
+
return realpathSync3(path);
|
|
2295
|
+
} catch {
|
|
2296
|
+
return resolve3(path);
|
|
2297
|
+
}
|
|
2298
|
+
};
|
|
2299
|
+
var RELEASE = /^v?(\d+)\.(\d+)\.(\d+)/;
|
|
2300
|
+
var isAhead = (installed, train) => {
|
|
2301
|
+
const mine = RELEASE.exec(installed);
|
|
2302
|
+
const theirs = RELEASE.exec(train);
|
|
2303
|
+
if (mine === null || theirs === null) return false;
|
|
2304
|
+
for (let at = 1; at <= 3; at += 1) {
|
|
2305
|
+
const step = Number(mine[at]) - Number(theirs[at]);
|
|
2306
|
+
if (step !== 0) return step > 0;
|
|
2307
|
+
}
|
|
2308
|
+
return false;
|
|
2309
|
+
};
|
|
2310
|
+
var isUserScope = (one) => one.scope === void 0 || one.scope === USER_SCOPE;
|
|
2311
|
+
var loadedAt = (installs, root) => {
|
|
2312
|
+
const here = real3(root);
|
|
2313
|
+
const scopedHere = installs.filter(
|
|
2314
|
+
(one) => !isUserScope(one) && one.projectPath !== void 0 && real3(one.projectPath) === here
|
|
2315
|
+
);
|
|
2316
|
+
return scopedHere.length > 0 ? scopedHere : installs.filter(isUserScope);
|
|
2087
2317
|
};
|
|
2088
2318
|
var checkClaudePlugin = ({
|
|
2089
2319
|
candidate = false,
|
|
2090
2320
|
home = homedir2(),
|
|
2091
2321
|
name = PLUGIN_NAME,
|
|
2322
|
+
root,
|
|
2092
2323
|
train
|
|
2093
2324
|
}) => {
|
|
2094
2325
|
if (train === void 0) return [];
|
|
2095
|
-
const
|
|
2326
|
+
const subject = `${name} (Claude plugin)`;
|
|
2327
|
+
const installed = installedPlugins(home, name);
|
|
2096
2328
|
if (installed.length === 0) {
|
|
2097
2329
|
return [
|
|
2098
2330
|
{
|
|
2099
2331
|
check: "loaded",
|
|
2100
2332
|
message: `this machine has no "${name}" plugin installed, so none of this release's hooks, agents or skills is loaded in any session here \u2014 a bump moved the packages and nothing moved them. Install it, or say out loud that this repo runs without the plugin`,
|
|
2101
|
-
subject
|
|
2333
|
+
subject,
|
|
2102
2334
|
verdict: "WARN"
|
|
2103
2335
|
}
|
|
2104
2336
|
];
|
|
2105
2337
|
}
|
|
2106
|
-
const
|
|
2338
|
+
const loaded = root === void 0 ? installed.filter(isUserScope) : loadedAt(installed, root);
|
|
2339
|
+
if (loaded.length === 0) {
|
|
2340
|
+
const elsewhere = installed.map((one) => `${one.version} for ${one.projectPath ?? "an unnamed project"}`).join(", ");
|
|
2341
|
+
return [
|
|
2342
|
+
{
|
|
2343
|
+
check: "loaded",
|
|
2344
|
+
message: `the "${name}" plugin is installed only at project scope for other paths (${elsewhere}), so no session opened here loads it \u2014 none of this release's hooks, agents or skills runs in this repo. Install it at user scope, or for this project`,
|
|
2345
|
+
subject,
|
|
2346
|
+
verdict: "WARN"
|
|
2347
|
+
}
|
|
2348
|
+
];
|
|
2349
|
+
}
|
|
2350
|
+
const drifted = loaded.filter((one) => one.version !== train).map((one) => one.version);
|
|
2107
2351
|
if (drifted.length === 0) return [];
|
|
2108
2352
|
if (candidate) {
|
|
2109
2353
|
return [
|
|
2110
2354
|
{
|
|
2111
2355
|
check: "loaded",
|
|
2112
2356
|
message: `the packages here are ${train}, a candidate the registry does not have, and the plugin is ${drifted.join(", ")} \u2014 the marketplace is where \`claude plugin update\` looks, so the plugin CANNOT follow a candidate and this is not drift. It becomes a real question the moment ${train} publishes`,
|
|
2113
|
-
subject
|
|
2357
|
+
subject,
|
|
2114
2358
|
verdict: "OK"
|
|
2115
2359
|
}
|
|
2116
2360
|
];
|
|
2117
2361
|
}
|
|
2362
|
+
if (drifted.every((one) => isAhead(one, train))) {
|
|
2363
|
+
return [
|
|
2364
|
+
{
|
|
2365
|
+
check: "loaded",
|
|
2366
|
+
message: `the installed plugin is ${drifted.join(", ")} and the packages here are ${train} \u2014 the plugin is ahead, so its hooks, agents and skills can name commands and flags these packages do not have yet. Bump the packages to it, or install the plugin at ${train}`,
|
|
2367
|
+
subject,
|
|
2368
|
+
verdict: "WARN"
|
|
2369
|
+
}
|
|
2370
|
+
];
|
|
2371
|
+
}
|
|
2118
2372
|
return [
|
|
2119
2373
|
{
|
|
2120
2374
|
check: "loaded",
|
|
2121
2375
|
message: `the installed plugin is ${drifted.join(", ")} and the packages here are ${train} \u2014 the plugin carries the hooks, the agents and the skills, and it moves on \`claude plugin update\`, never on an install. Every session in this repo is running the older ones`,
|
|
2122
|
-
subject
|
|
2376
|
+
subject,
|
|
2123
2377
|
verdict: "WARN"
|
|
2124
2378
|
}
|
|
2125
2379
|
];
|
|
2126
2380
|
};
|
|
2127
2381
|
|
|
2128
2382
|
// src/loaded.ts
|
|
2129
|
-
import { readFileSync as
|
|
2130
|
-
import { join as
|
|
2383
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
2384
|
+
import { join as join14, sep as sep4 } from "path";
|
|
2131
2385
|
var SCOPE2 = "@geonosis/";
|
|
2132
2386
|
var NOT_THE_SCOPE_DIRECTORY = "Deleting only the scope directory inside that node_modules is a repair no pnpm install undoes \u2014 the tree is then missing the package and every install stays green.";
|
|
2133
2387
|
var BLOCKS = [
|
|
@@ -2136,7 +2390,7 @@ var BLOCKS = [
|
|
|
2136
2390
|
"optionalDependencies",
|
|
2137
2391
|
"peerDependencies"
|
|
2138
2392
|
];
|
|
2139
|
-
var
|
|
2393
|
+
var RELEASE2 = /^v?(\d+)\.(\d+)\.(\d+)$/;
|
|
2140
2394
|
var PINNED = /^[=v]?(\d+\.\d+\.\d+(?:[-+][\w.-]+)?)$/;
|
|
2141
2395
|
var RANGE = /^(\^|~|>=)\s*v?(\d+)\.(\d+)\.(\d+)$/;
|
|
2142
2396
|
var LINKED = /^(?:file|link|portal|workspace):/;
|
|
@@ -2162,7 +2416,7 @@ var satisfies = (version, spec) => {
|
|
|
2162
2416
|
const pinned = PINNED.exec(wanted);
|
|
2163
2417
|
return pinned === null ? void 0 : pinned[1] === version.replace(/^v/, "");
|
|
2164
2418
|
}
|
|
2165
|
-
const here =
|
|
2419
|
+
const here = RELEASE2.exec(version);
|
|
2166
2420
|
if (here === null) return void 0;
|
|
2167
2421
|
const at = parts(here);
|
|
2168
2422
|
const bound = [
|
|
@@ -2184,14 +2438,14 @@ var specIn = (manifest, specifier) => {
|
|
|
2184
2438
|
}
|
|
2185
2439
|
return void 0;
|
|
2186
2440
|
};
|
|
2187
|
-
var
|
|
2441
|
+
var holds3 = (parent, child) => child === parent || child.startsWith(`${parent}${sep4}`);
|
|
2188
2442
|
var manifestNameOf = (workspace) => workspace.relative === "" ? "package.json" : `${workspace.relative}/package.json`;
|
|
2189
2443
|
var declaredFor = ({
|
|
2190
2444
|
dir,
|
|
2191
2445
|
specifier,
|
|
2192
2446
|
workspaces
|
|
2193
2447
|
}) => {
|
|
2194
|
-
const upwards = workspaces.filter((one) =>
|
|
2448
|
+
const upwards = workspaces.filter((one) => holds3(one.dir, dir)).toSorted((a, b) => b.dir.length - a.dir.length);
|
|
2195
2449
|
for (const workspace of upwards) {
|
|
2196
2450
|
const spec = specIn(workspace.manifest, specifier);
|
|
2197
2451
|
if (spec !== void 0) return { at: manifestNameOf(workspace), spec };
|
|
@@ -2236,7 +2490,7 @@ var oneConfig = async ({
|
|
|
2236
2490
|
}
|
|
2237
2491
|
return held ? said2("OK", `loaded ${loaded.version} = declared ${declared.spec} (${declared.at})`) : said2(
|
|
2238
2492
|
"FAIL",
|
|
2239
|
-
`loaded ${loaded.version}, declared ${declared.spec} (${declared.at}) \u2014 a nested copy at ${loaded.at}. Run \`geonosis update --to ${declared.spec}\`: it removes the WHOLE node_modules of every non-root workspace carrying one, never the root's, and reinstalls. ${NOT_THE_SCOPE_DIRECTORY} The linter is not running what the tree declares until then`
|
|
2493
|
+
`loaded ${loaded.version}, declared ${declared.spec} (${declared.at}) \u2014 a nested copy at ${loaded.at}. Run \`geonosis update --to ${declared.spec.replace(/^(?:\^|~|>=|=)\s*v?/, "")}\`: it removes the WHOLE node_modules of every non-root workspace carrying one, never the root's, and reinstalls. ${NOT_THE_SCOPE_DIRECTORY} The linter is not running what the tree declares until then`
|
|
2240
2494
|
);
|
|
2241
2495
|
};
|
|
2242
2496
|
var labelOf2 = (workspace) => workspace.relative === "" ? "root" : workspace.relative;
|
|
@@ -2275,7 +2529,7 @@ var shipperOf = (root, bin) => {
|
|
|
2275
2529
|
const name = bin === "geonosis" ? "@geonosis/cli" : `@geonosis/${bin.replace(/^geonosis-/, "")}`;
|
|
2276
2530
|
try {
|
|
2277
2531
|
const manifest = JSON.parse(
|
|
2278
|
-
|
|
2532
|
+
readFileSync13(join14(packageDirOf(resolveFrom(root, name, root), name), "package.json"), "utf8")
|
|
2279
2533
|
);
|
|
2280
2534
|
const declared = manifest.bin;
|
|
2281
2535
|
return typeof declared === "object" && !Object.hasOwn(declared, bin) ? "" : name;
|
|
@@ -2365,6 +2619,7 @@ var checkLoaded = async ({
|
|
|
2365
2619
|
...checkClaudePlugin({
|
|
2366
2620
|
candidate: installedFromCandidate(workspaces),
|
|
2367
2621
|
home,
|
|
2622
|
+
root,
|
|
2368
2623
|
train: await trainVersion(root, [...specifiers])
|
|
2369
2624
|
})
|
|
2370
2625
|
);
|
|
@@ -2382,56 +2637,47 @@ var trainVersion = async (root, specifiers) => {
|
|
|
2382
2637
|
};
|
|
2383
2638
|
|
|
2384
2639
|
// src/observability.ts
|
|
2385
|
-
import { readFileSync as
|
|
2386
|
-
import { join as
|
|
2387
|
-
var GEONOSIS_FILE2 = "geonosis.json";
|
|
2640
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
2641
|
+
import { join as join15 } from "path";
|
|
2388
2642
|
var REACHES_NOTHING = /* @__PURE__ */ new Set(["console", "memory", "noop", "none", "null", "swallowing"]);
|
|
2389
2643
|
var DEFAULT_MAX_AGE_SECONDS = 3600;
|
|
2390
2644
|
var HEAD_TIMEOUT_MS = 3e3;
|
|
2391
2645
|
var make3 = findingMaker("observability");
|
|
2392
2646
|
var finding9 = (verdict, subject, message) => make3(subject, verdict, message);
|
|
2393
|
-
var
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2647
|
+
var readGeonosis = (root) => {
|
|
2648
|
+
const read = readGeonosisFile(root);
|
|
2649
|
+
if (read.kind === "unreadable") return { error: read.error, present: false };
|
|
2650
|
+
if (read.kind === "absent") return { present: false };
|
|
2651
|
+
const block = read.config.observability;
|
|
2652
|
+
if (typeof block !== "object" || block === null || Array.isArray(block)) {
|
|
2398
2653
|
return { present: false };
|
|
2399
2654
|
}
|
|
2400
|
-
|
|
2401
|
-
const parsed = JSON.parse(text);
|
|
2402
|
-
const block = parsed.observability;
|
|
2403
|
-
if (typeof block !== "object" || block === null || Array.isArray(block)) {
|
|
2404
|
-
return { present: false };
|
|
2405
|
-
}
|
|
2406
|
-
return { config: block, present: true };
|
|
2407
|
-
} catch (error) {
|
|
2408
|
-
return { error: error.message, present: false };
|
|
2409
|
-
}
|
|
2655
|
+
return { config: block, present: true };
|
|
2410
2656
|
};
|
|
2411
2657
|
var exporterFinding = (config) => {
|
|
2412
2658
|
const sink = config.sink;
|
|
2413
2659
|
if (typeof sink !== "string" || sink.trim() === "") {
|
|
2414
2660
|
return finding9(
|
|
2415
2661
|
"FAIL",
|
|
2416
|
-
|
|
2662
|
+
GEONOSIS_FILE,
|
|
2417
2663
|
"observability.sink is not set, so nothing here says where errors are supposed to go \u2014 and a repo that cannot name its exporter has not got one"
|
|
2418
2664
|
);
|
|
2419
2665
|
}
|
|
2420
2666
|
if (REACHES_NOTHING.has(sink.toLowerCase())) {
|
|
2421
2667
|
return finding9(
|
|
2422
2668
|
"WARN",
|
|
2423
|
-
|
|
2669
|
+
GEONOSIS_FILE,
|
|
2424
2670
|
`the configured sink is "${sink}", which answers ok and reaches nothing. Correct in a dev tree; in a deployed one it is the instrument that cannot fail.`
|
|
2425
2671
|
);
|
|
2426
2672
|
}
|
|
2427
|
-
return finding9("OK",
|
|
2673
|
+
return finding9("OK", GEONOSIS_FILE, `the configured sink is "${sink}"`);
|
|
2428
2674
|
};
|
|
2429
2675
|
var reachableFinding = async (config) => {
|
|
2430
2676
|
const endpoint = config.endpoint;
|
|
2431
2677
|
if (typeof endpoint !== "string" || endpoint.trim() === "") {
|
|
2432
2678
|
return finding9(
|
|
2433
2679
|
"SKIP",
|
|
2434
|
-
|
|
2680
|
+
GEONOSIS_FILE,
|
|
2435
2681
|
"no observability.endpoint was named, so whether the exporter is reachable was not asked"
|
|
2436
2682
|
);
|
|
2437
2683
|
}
|
|
@@ -2441,32 +2687,41 @@ var reachableFinding = async (config) => {
|
|
|
2441
2687
|
const response = await fetch(endpoint, { method: "HEAD", signal: controller.signal });
|
|
2442
2688
|
return finding9(
|
|
2443
2689
|
"OK",
|
|
2444
|
-
|
|
2690
|
+
GEONOSIS_FILE,
|
|
2445
2691
|
`${endpoint} is reachable \u2014 it answered ${response.status} to a HEAD`
|
|
2446
2692
|
);
|
|
2447
2693
|
} catch (error) {
|
|
2448
2694
|
return finding9(
|
|
2449
2695
|
"FAIL",
|
|
2450
|
-
|
|
2696
|
+
GEONOSIS_FILE,
|
|
2451
2697
|
`${endpoint} is not reachable from here: ${error.message}. Every report this repo sends is going into that.`
|
|
2452
2698
|
);
|
|
2453
2699
|
} finally {
|
|
2454
2700
|
clearTimeout(timer);
|
|
2455
2701
|
}
|
|
2456
2702
|
};
|
|
2703
|
+
var reachesNothing = (config) => typeof config.sink === "string" && REACHES_NOTHING.has(config.sink.toLowerCase()) ? config.sink : void 0;
|
|
2457
2704
|
var ageFinding = (config, root, now) => {
|
|
2458
2705
|
const file = config.lastEventFile;
|
|
2706
|
+
const dead = reachesNothing(config);
|
|
2707
|
+
if (dead !== void 0) {
|
|
2708
|
+
return finding9(
|
|
2709
|
+
"SKIP",
|
|
2710
|
+
GEONOSIS_FILE,
|
|
2711
|
+
`the sink is "${dead}", which reaches nothing, so the age of its last event says when this checkout last ran, not whether anything was delivered \u2014 not judged. Point a real sink here and it will be.`
|
|
2712
|
+
);
|
|
2713
|
+
}
|
|
2459
2714
|
if (typeof file !== "string" || file.trim() === "") {
|
|
2460
2715
|
return finding9(
|
|
2461
2716
|
"SKIP",
|
|
2462
|
-
|
|
2717
|
+
GEONOSIS_FILE,
|
|
2463
2718
|
"no observability.lastEventFile was configured, so when the last event arrived is not a question anything here can answer. Have the sink write { at, id, sink } on every capture and name the file."
|
|
2464
2719
|
);
|
|
2465
2720
|
}
|
|
2466
2721
|
const maxAgeSeconds = typeof config.maxAgeSeconds === "number" && config.maxAgeSeconds > 0 ? config.maxAgeSeconds : DEFAULT_MAX_AGE_SECONDS;
|
|
2467
2722
|
let record;
|
|
2468
2723
|
try {
|
|
2469
|
-
record = JSON.parse(
|
|
2724
|
+
record = JSON.parse(readFileSync14(join15(root, file), "utf8"));
|
|
2470
2725
|
} catch (error) {
|
|
2471
2726
|
return finding9(
|
|
2472
2727
|
"FAIL",
|
|
@@ -2494,19 +2749,27 @@ var ageFinding = (config, root, now) => {
|
|
|
2494
2749
|
};
|
|
2495
2750
|
var probeFinding = (config) => {
|
|
2496
2751
|
const probe = config.probe;
|
|
2752
|
+
const dead = reachesNothing(config);
|
|
2753
|
+
if (dead !== void 0) {
|
|
2754
|
+
return finding9(
|
|
2755
|
+
"SKIP",
|
|
2756
|
+
GEONOSIS_FILE,
|
|
2757
|
+
`the sink is "${dead}", which reaches nothing, so no probe can prove a report reached it \u2014 not asked`
|
|
2758
|
+
);
|
|
2759
|
+
}
|
|
2497
2760
|
if (typeof probe === "string" && probe.trim() !== "") {
|
|
2498
|
-
return finding9("OK",
|
|
2761
|
+
return finding9("OK", GEONOSIS_FILE, `the probe that proves this exporter is "${probe}"`);
|
|
2499
2762
|
}
|
|
2500
2763
|
if (typeof config.lastEventFile === "string" && config.lastEventFile.trim() !== "") {
|
|
2501
2764
|
return finding9(
|
|
2502
2765
|
"OK",
|
|
2503
|
-
|
|
2766
|
+
GEONOSIS_FILE,
|
|
2504
2767
|
"no probe command, but a last event file is read above, so something does look at this exporter"
|
|
2505
2768
|
);
|
|
2506
2769
|
}
|
|
2507
2770
|
return finding9(
|
|
2508
2771
|
"WARN",
|
|
2509
|
-
|
|
2772
|
+
GEONOSIS_FILE,
|
|
2510
2773
|
"neither observability.probe nor observability.lastEventFile is configured, so nothing in this repo has ever established that a report reaches the sink. Name a probe command \u2014 the doctor reports it, your gate runs it."
|
|
2511
2774
|
);
|
|
2512
2775
|
};
|
|
@@ -2514,13 +2777,13 @@ var checkObservability = async ({
|
|
|
2514
2777
|
now,
|
|
2515
2778
|
root
|
|
2516
2779
|
}) => {
|
|
2517
|
-
const read =
|
|
2780
|
+
const read = readGeonosis(root);
|
|
2518
2781
|
if (read.error !== void 0) {
|
|
2519
2782
|
return [
|
|
2520
2783
|
finding9(
|
|
2521
2784
|
"FAIL",
|
|
2522
|
-
|
|
2523
|
-
`${
|
|
2785
|
+
GEONOSIS_FILE,
|
|
2786
|
+
`${GEONOSIS_FILE} could not be parsed: ${read.error}. A config nobody can read has not been read, and every question below would have been answered from a default nobody chose.`
|
|
2524
2787
|
)
|
|
2525
2788
|
];
|
|
2526
2789
|
}
|
|
@@ -2528,8 +2791,8 @@ var checkObservability = async ({
|
|
|
2528
2791
|
return [
|
|
2529
2792
|
finding9(
|
|
2530
2793
|
"SKIP",
|
|
2531
|
-
|
|
2532
|
-
`no observability block in ${
|
|
2794
|
+
GEONOSIS_FILE,
|
|
2795
|
+
`no observability block in ${GEONOSIS_FILE}, so nothing here knows where this repo sends its errors. Add { sink, endpoint, lastEventFile | probe, maxAgeSeconds } to have this asked.`
|
|
2533
2796
|
)
|
|
2534
2797
|
];
|
|
2535
2798
|
}
|
|
@@ -2542,19 +2805,77 @@ var checkObservability = async ({
|
|
|
2542
2805
|
];
|
|
2543
2806
|
};
|
|
2544
2807
|
|
|
2808
|
+
// src/repo-corpus.ts
|
|
2809
|
+
import { join as join16 } from "path";
|
|
2810
|
+
var corpusFrom = (config, root) => {
|
|
2811
|
+
const declared = config.doctor?.corpus;
|
|
2812
|
+
return typeof declared === "string" && declared !== "" ? join16(root, declared) : void 0;
|
|
2813
|
+
};
|
|
2814
|
+
var repoCorpusOf = (root) => {
|
|
2815
|
+
const read = readGeonosisFile(root);
|
|
2816
|
+
if (read.kind === "unreadable") {
|
|
2817
|
+
throw new DoctorError(`${GEONOSIS_FILE} could not be parsed: ${read.error}`);
|
|
2818
|
+
}
|
|
2819
|
+
return read.kind === "present" ? corpusFrom(read.config, root) : void 0;
|
|
2820
|
+
};
|
|
2821
|
+
|
|
2822
|
+
// src/required-options.ts
|
|
2823
|
+
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
2824
|
+
var optionsOf2 = (level) => Array.isArray(level) ? level.slice(1) : [];
|
|
2825
|
+
var refusesBare = (rule) => missingOptionOf(rule, []) !== void 0;
|
|
2826
|
+
var configured = (config, namespace) => [config.rules, ...config.overrides.map((one) => one.rules)].flatMap(
|
|
2827
|
+
(rules) => enabledRulesOf(rules, namespace).map((id) => [id, optionsOf2(rules[id])])
|
|
2828
|
+
);
|
|
2829
|
+
var checkRequiredOptions = async ({
|
|
2830
|
+
config,
|
|
2831
|
+
entry
|
|
2832
|
+
}) => {
|
|
2833
|
+
const loaded = await import(pathToFileURL3(entry).href).catch(() => void 0);
|
|
2834
|
+
const namespace = loaded?.default?.meta?.name;
|
|
2835
|
+
const rules = loaded?.default?.rules;
|
|
2836
|
+
if (typeof namespace !== "string" || namespace === "" || rules === void 0) return [];
|
|
2837
|
+
const enabled = configured(config, namespace);
|
|
2838
|
+
if (enabled.length === 0) return [];
|
|
2839
|
+
const missing = [];
|
|
2840
|
+
const said2 = /* @__PURE__ */ new Set();
|
|
2841
|
+
const gated = /* @__PURE__ */ new Set();
|
|
2842
|
+
for (const [id, options] of enabled) {
|
|
2843
|
+
const rule = rules[id.slice(namespace.length + 1)];
|
|
2844
|
+
if (rule === void 0) continue;
|
|
2845
|
+
if (refusesBare(rule)) gated.add(id);
|
|
2846
|
+
const option = missingOptionOf(rule, options);
|
|
2847
|
+
if (option === void 0 || said2.has(`${id} ${option}`)) continue;
|
|
2848
|
+
said2.add(`${id} ${option}`);
|
|
2849
|
+
missing.push({
|
|
2850
|
+
check: "loaded",
|
|
2851
|
+
message: `it is enabled here with no \`${option}\`, and the rule cannot judge a file without one \u2014 every run reports only that it is missing, and nothing about the tree. Configure \`${option}\`, or turn the rule off`,
|
|
2852
|
+
subject: `${config.relative} \u2192 ${id}`,
|
|
2853
|
+
verdict: "FAIL"
|
|
2854
|
+
});
|
|
2855
|
+
}
|
|
2856
|
+
if (missing.length > 0) return missing;
|
|
2857
|
+
return [
|
|
2858
|
+
{
|
|
2859
|
+
check: "loaded",
|
|
2860
|
+
message: gated.size === 1 ? "1 rule requires options, 1 carries them" : `${gated.size} rules require options, ${gated.size} carry them`,
|
|
2861
|
+
subject: config.relative,
|
|
2862
|
+
verdict: "OK"
|
|
2863
|
+
}
|
|
2864
|
+
];
|
|
2865
|
+
};
|
|
2866
|
+
|
|
2545
2867
|
// src/runner.ts
|
|
2546
|
-
import {
|
|
2547
|
-
import {
|
|
2868
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
2869
|
+
import { resolve as resolve4 } from "path";
|
|
2548
2870
|
var TEST_FAILURES = "testFailures";
|
|
2549
2871
|
var RUNS_A_RUNNER = /(?:^|[\s;&|(])(?:npx\s+|bunx\s+|pnpm\s+(?:exec\s+)?)?(?:vitest|bun\s+test)(?:\s|$)/;
|
|
2550
2872
|
var RUNS_BUN_TEST = /(?:^|[\s;&|(])(?:bunx\s+)?bun\s+test(?:\s|$)/;
|
|
2551
2873
|
var OUTPUT_FILE = /--outputFile[= ](\S+)/i;
|
|
2552
|
-
var A_SCRIPT_FILE = /\.(?:[cm]?[jt]sx?|sh|bash)$/;
|
|
2553
2874
|
var wrapperNamedBy = (script, dir) => {
|
|
2554
2875
|
for (const token of script.split(/[\s'"]+/)) {
|
|
2555
2876
|
if (!A_SCRIPT_FILE.test(token)) continue;
|
|
2556
2877
|
try {
|
|
2557
|
-
return { source:
|
|
2878
|
+
return { source: readFileSync15(resolve4(dir, token), "utf8"), where: token };
|
|
2558
2879
|
} catch {
|
|
2559
2880
|
continue;
|
|
2560
2881
|
}
|
|
@@ -2579,34 +2900,13 @@ var reportingCounters = (ratchet) => (ratchet?.counters ?? []).filter(
|
|
|
2579
2900
|
var keyOf = (entry) => stringOf(entry.key) === "" ? TEST_FAILURES : stringOf(entry.key);
|
|
2580
2901
|
var namesReport = (entry, path) => stringOf(entry.reportPath) === path || stringOf(entry.command).includes(path);
|
|
2581
2902
|
var PROVES = /--prove\b/;
|
|
2582
|
-
var everythingRun2 = (root, workspaces) => {
|
|
2583
|
-
const scripts = workspaces.flatMap((one) => Object.values(one.manifest.scripts ?? {}));
|
|
2584
|
-
const dir = join14(root, ".github/workflows");
|
|
2585
|
-
const workflows = (existsSync11(dir) ? readdirSync5(dir) : []).filter((name) => name.endsWith(".yml") || name.endsWith(".yaml")).map((name) => {
|
|
2586
|
-
try {
|
|
2587
|
-
return readFileSync14(join14(dir, name), "utf8");
|
|
2588
|
-
} catch {
|
|
2589
|
-
return "";
|
|
2590
|
-
}
|
|
2591
|
-
});
|
|
2592
|
-
let tiers = {};
|
|
2593
|
-
try {
|
|
2594
|
-
const config = JSON.parse(readFileSync14(join14(root, "geonosis.json"), "utf8"));
|
|
2595
|
-
tiers = config.verify ?? {};
|
|
2596
|
-
} catch {
|
|
2597
|
-
}
|
|
2598
|
-
return {
|
|
2599
|
-
run: [...Object.values(tiers).flat(), ...scripts, ...workflows].join("\n"),
|
|
2600
|
-
tiers: Object.keys(tiers)
|
|
2601
|
-
};
|
|
2602
|
-
};
|
|
2603
2903
|
var proveIsWired = ({
|
|
2604
2904
|
ratchet,
|
|
2605
2905
|
root,
|
|
2606
2906
|
workspaces
|
|
2607
2907
|
}) => {
|
|
2608
2908
|
if (ratchet === void 0 || ratchet.counters.length === 0) return [];
|
|
2609
|
-
const { run, tiers } =
|
|
2909
|
+
const { run, tiers } = everythingRun(root, workspaces);
|
|
2610
2910
|
if (PROVES.test(run)) {
|
|
2611
2911
|
return [
|
|
2612
2912
|
finding10(
|
|
@@ -2690,33 +2990,31 @@ var checkRunner = ({
|
|
|
2690
2990
|
|
|
2691
2991
|
// src/doctor.ts
|
|
2692
2992
|
import { homedir as homedir3 } from "os";
|
|
2693
|
-
import { join as
|
|
2993
|
+
import { join as join19 } from "path";
|
|
2694
2994
|
import { resolveOxlint } from "@geonosis/lint-parity";
|
|
2695
2995
|
|
|
2696
2996
|
// src/engine.ts
|
|
2697
|
-
import { sep as
|
|
2698
|
-
var
|
|
2699
|
-
[
|
|
2700
|
-
manifest.dependencies,
|
|
2701
|
-
manifest.devDependencies,
|
|
2702
|
-
manifest.optionalDependencies,
|
|
2703
|
-
manifest.peerDependencies
|
|
2704
|
-
].flatMap((block) => Object.keys(block ?? {}))
|
|
2705
|
-
);
|
|
2706
|
-
var holds3 = (parent, child) => child === parent || child.startsWith(`${parent}${sep4}`);
|
|
2997
|
+
import { sep as sep5 } from "path";
|
|
2998
|
+
var holds4 = (parent, child) => child === parent || child.startsWith(`${parent}${sep5}`);
|
|
2707
2999
|
var labelOf3 = (workspace) => workspace.relative === "" ? "root" : workspace.relative;
|
|
2708
|
-
var governedBy = (dir, workspaces) => workspaces.filter((one) =>
|
|
2709
|
-
var enabledHere2 = (config, plugin) => [
|
|
2710
|
-
.../* @__PURE__ */ new Set([
|
|
2711
|
-
...enabledRulesOf(config.rules, plugin),
|
|
2712
|
-
...config.overrides.flatMap((one) => enabledRulesOf(one.rules, plugin))
|
|
2713
|
-
])
|
|
2714
|
-
];
|
|
3000
|
+
var governedBy = (dir, workspaces) => workspaces.filter((one) => holds4(one.dir, dir) || holds4(dir, one.dir));
|
|
2715
3001
|
var checkEngines = async ({
|
|
2716
3002
|
config,
|
|
2717
3003
|
entry,
|
|
3004
|
+
evidence = [],
|
|
2718
3005
|
workspaces
|
|
2719
3006
|
}) => {
|
|
3007
|
+
const holding = evidenceHolding(config, evidence);
|
|
3008
|
+
if (holding !== void 0) {
|
|
3009
|
+
return [
|
|
3010
|
+
{
|
|
3011
|
+
check: "exercised",
|
|
3012
|
+
message: "evidence, not a product config \u2014 its directory is one this doctor reads as a corpus of planted files, so it is not asked which engine its manifests presume",
|
|
3013
|
+
subject: config.relative,
|
|
3014
|
+
verdict: "SKIP"
|
|
3015
|
+
}
|
|
3016
|
+
];
|
|
3017
|
+
}
|
|
2720
3018
|
const plugin = await presumptionsOf(entry).catch(() => void 0);
|
|
2721
3019
|
if (plugin === void 0 || plugin.namespace === "" || Object.keys(plugin.presumed).length === 0 && Object.keys(plugin.floors).length === 0) {
|
|
2722
3020
|
return [];
|
|
@@ -2734,9 +3032,9 @@ var checkEngines = async ({
|
|
|
2734
3032
|
];
|
|
2735
3033
|
};
|
|
2736
3034
|
const governed = governedBy(config.dir, workspaces);
|
|
2737
|
-
const declared = new Set(governed.flatMap((one) => [...
|
|
3035
|
+
const declared = new Set(governed.flatMap((one) => [...dependencyNamesOf(one.manifest)]));
|
|
2738
3036
|
const asked = governed.map(labelOf3).join(", ");
|
|
2739
|
-
return
|
|
3037
|
+
return enabledHere(config, plugin.namespace).flatMap((rule) => {
|
|
2740
3038
|
const floorless = pending(rule);
|
|
2741
3039
|
const presumes = plugin.presumed[rule.slice(plugin.namespace.length + 1)];
|
|
2742
3040
|
if (presumes === void 0 || presumes.packages.some((name) => declared.has(name))) {
|
|
@@ -2754,26 +3052,49 @@ var checkEngines = async ({
|
|
|
2754
3052
|
});
|
|
2755
3053
|
};
|
|
2756
3054
|
|
|
2757
|
-
// src/
|
|
2758
|
-
import {
|
|
2759
|
-
import {
|
|
2760
|
-
|
|
2761
|
-
var
|
|
2762
|
-
var
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
3055
|
+
// src/formatter.ts
|
|
3056
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
3057
|
+
import { existsSync as existsSync11, readdirSync as readdirSync6 } from "fs";
|
|
3058
|
+
import { join as join17 } from "path";
|
|
3059
|
+
var KIT_RULES = ".claude/rules/geonosis";
|
|
3060
|
+
var finding11 = findingMaker("formatter");
|
|
3061
|
+
var ALL_PATHS_IGNORED = 2;
|
|
3062
|
+
var checkFormatter = ({ root }) => {
|
|
3063
|
+
if (!existsSync11(join17(root, KIT_RULES))) {
|
|
3064
|
+
return [
|
|
3065
|
+
finding11(KIT_RULES, "SKIP", "no kit rules are installed here for a formatter to rewrite")
|
|
3066
|
+
];
|
|
2768
3067
|
}
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
3068
|
+
const oxfmt = join17(root, "node_modules/.bin/oxfmt");
|
|
3069
|
+
if (!existsSync11(oxfmt)) {
|
|
3070
|
+
return [finding11(KIT_RULES, "SKIP", "this repo has no oxfmt, the formatter the kit speaks for")];
|
|
3071
|
+
}
|
|
3072
|
+
const files = readdirSync6(join17(root, KIT_RULES)).filter((name) => name.endsWith(".md")).map((name) => join17(KIT_RULES, name));
|
|
3073
|
+
const run = spawnSync3(oxfmt, ["--list-different", ...files], { cwd: root, encoding: "utf8" });
|
|
3074
|
+
const named2 = run.stdout.split("\n").filter((line) => files.includes(line.trim()));
|
|
3075
|
+
if (run.status === ALL_PATHS_IGNORED && run.stderr.includes("excluded by ignore rules")) {
|
|
3076
|
+
return [finding11(KIT_RULES, "OK", "oxfmt leaves the kit\u2019s rule files alone")];
|
|
3077
|
+
}
|
|
3078
|
+
if (run.status === 0 || run.status === 1 && named2.length > 0) {
|
|
3079
|
+
return [
|
|
3080
|
+
finding11(
|
|
3081
|
+
KIT_RULES,
|
|
3082
|
+
"WARN",
|
|
3083
|
+
`oxfmt would format the kit's rule files, and the next \`format\` rewrites them \u2014 tiers.md is generated. Add "${KIT_RULES}" to the ignorePatterns in .oxfmtrc.json`
|
|
3084
|
+
)
|
|
3085
|
+
];
|
|
3086
|
+
}
|
|
3087
|
+
return [
|
|
3088
|
+
finding11(
|
|
3089
|
+
KIT_RULES,
|
|
3090
|
+
"UNJUDGED",
|
|
3091
|
+
`oxfmt could not be asked (exit ${String(run.status)}): ${run.stderr.trim() || run.stdout.trim()}`
|
|
3092
|
+
)
|
|
3093
|
+
];
|
|
2776
3094
|
};
|
|
3095
|
+
|
|
3096
|
+
// src/path-grants.ts
|
|
3097
|
+
var LINTABLE2 = /\.[cm]?[jt]sx?$/;
|
|
2777
3098
|
var stringsOf2 = (value) => Array.isArray(value) ? value.filter((one) => typeof one === "string") : [];
|
|
2778
3099
|
var bagOf = (level) => {
|
|
2779
3100
|
if (!Array.isArray(level)) return {};
|
|
@@ -2795,7 +3116,9 @@ var checkPathGrants = ({
|
|
|
2795
3116
|
}) => {
|
|
2796
3117
|
const declared = Object.entries(grants);
|
|
2797
3118
|
if (declared.length === 0) return [];
|
|
2798
|
-
const files =
|
|
3119
|
+
const files = filesUnder(root, (name) => LINTABLE2.test(name)).map(
|
|
3120
|
+
(path) => relativePath(root, path)
|
|
3121
|
+
);
|
|
2799
3122
|
const found = [];
|
|
2800
3123
|
for (const [id, grant] of declared) {
|
|
2801
3124
|
for (const layer of layersOf(config, id)) {
|
|
@@ -2818,14 +3141,13 @@ var checkPathGrants = ({
|
|
|
2818
3141
|
};
|
|
2819
3142
|
|
|
2820
3143
|
// src/rails.ts
|
|
2821
|
-
import { existsSync as existsSync12, readFileSync as
|
|
2822
|
-
import { join as
|
|
2823
|
-
var GEONOSIS2 = "geonosis.json";
|
|
3144
|
+
import { existsSync as existsSync12, readFileSync as readFileSync16 } from "fs";
|
|
3145
|
+
import { join as join18 } from "path";
|
|
2824
3146
|
var PROJECT = ".claude/settings.json";
|
|
2825
3147
|
var LOCAL = ".claude/settings.local.json";
|
|
2826
3148
|
var RUN_RECORD = ".geonosis/rails-run.json";
|
|
2827
3149
|
var managedSettingsPath = (platform = process.platform) => platform === "darwin" ? "/Library/Application Support/ClaudeCode/managed-settings.json" : "/etc/claude-code/managed-settings.json";
|
|
2828
|
-
var
|
|
3150
|
+
var finding12 = findingMaker("rails");
|
|
2829
3151
|
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2830
3152
|
var networkIn = (parsed) => {
|
|
2831
3153
|
if (!isRecord3(parsed)) return void 0;
|
|
@@ -2843,7 +3165,7 @@ var sourceAt = (path, label) => {
|
|
|
2843
3165
|
if (!existsSync12(path)) return { network: void 0, path: label, unreadable: false };
|
|
2844
3166
|
try {
|
|
2845
3167
|
return {
|
|
2846
|
-
network: networkIn(JSON.parse(
|
|
3168
|
+
network: networkIn(JSON.parse(readFileSync16(path, "utf8"))),
|
|
2847
3169
|
path: label,
|
|
2848
3170
|
unreadable: false
|
|
2849
3171
|
};
|
|
@@ -2851,16 +3173,8 @@ var sourceAt = (path, label) => {
|
|
|
2851
3173
|
return { network: void 0, path: label, unreadable: true };
|
|
2852
3174
|
}
|
|
2853
3175
|
};
|
|
2854
|
-
var declaredEgress = (
|
|
2855
|
-
const
|
|
2856
|
-
if (!existsSync12(at)) return void 0;
|
|
2857
|
-
let parsed;
|
|
2858
|
-
try {
|
|
2859
|
-
parsed = JSON.parse(readFileSync15(at, "utf8"));
|
|
2860
|
-
} catch {
|
|
2861
|
-
return void 0;
|
|
2862
|
-
}
|
|
2863
|
-
const egress = parsed?.rails?.egress;
|
|
3176
|
+
var declaredEgress = (config) => {
|
|
3177
|
+
const egress = config.rails?.egress;
|
|
2864
3178
|
if (!isRecord3(egress)) return void 0;
|
|
2865
3179
|
const allow = egress["allow"];
|
|
2866
3180
|
return {
|
|
@@ -2868,10 +3182,10 @@ var declaredEgress = (root) => {
|
|
|
2868
3182
|
};
|
|
2869
3183
|
};
|
|
2870
3184
|
var deniedEgress = (root) => {
|
|
2871
|
-
const at =
|
|
3185
|
+
const at = join18(root, RUN_RECORD);
|
|
2872
3186
|
if (!existsSync12(at)) {
|
|
2873
3187
|
return [
|
|
2874
|
-
|
|
3188
|
+
finding12(
|
|
2875
3189
|
"deniedEgress",
|
|
2876
3190
|
"SKIP",
|
|
2877
3191
|
`there is no ${RUN_RECORD} here, so no run has been recorded for this to be a gate over`
|
|
@@ -2880,23 +3194,32 @@ var deniedEgress = (root) => {
|
|
|
2880
3194
|
}
|
|
2881
3195
|
let parsed;
|
|
2882
3196
|
try {
|
|
2883
|
-
parsed = JSON.parse(
|
|
3197
|
+
parsed = JSON.parse(readFileSync16(at, "utf8"));
|
|
2884
3198
|
} catch (error) {
|
|
2885
3199
|
return [
|
|
2886
|
-
|
|
3200
|
+
finding12(
|
|
2887
3201
|
"deniedEgress",
|
|
2888
3202
|
"FAIL",
|
|
2889
3203
|
`${RUN_RECORD} is not readable JSON (${error.message}) \u2014 a record nobody can read is not a record of zero denied attempts`
|
|
2890
3204
|
)
|
|
2891
3205
|
];
|
|
2892
3206
|
}
|
|
2893
|
-
|
|
3207
|
+
if (!isRecord3(parsed) || !Array.isArray(parsed["deniedEgress"])) {
|
|
3208
|
+
return [
|
|
3209
|
+
finding12(
|
|
3210
|
+
"deniedEgress",
|
|
3211
|
+
"FAIL",
|
|
3212
|
+
`${RUN_RECORD} lists no deniedEgress \u2014 a record that does not say what it records is not a record of zero denied attempts`
|
|
3213
|
+
)
|
|
3214
|
+
];
|
|
3215
|
+
}
|
|
3216
|
+
const denied = parsed["deniedEgress"];
|
|
2894
3217
|
if (denied.length === 0) {
|
|
2895
|
-
return [
|
|
3218
|
+
return [finding12("deniedEgress", "OK", "no run recorded a denied egress attempt")];
|
|
2896
3219
|
}
|
|
2897
3220
|
const hosts = denied.map((one) => isRecord3(one) && typeof one["host"] === "string" ? one["host"] : "(unnamed)").join(", ");
|
|
2898
3221
|
return [
|
|
2899
|
-
|
|
3222
|
+
finding12(
|
|
2900
3223
|
"deniedEgress",
|
|
2901
3224
|
"FAIL",
|
|
2902
3225
|
`${denied.length} denied egress attempt(s) in this run's record \u2014 ${hosts}. This is a gate at ZERO and never a counter to hold flat: a run that reached for a host this repo does not allow is a run somebody reads, whatever the number was yesterday`
|
|
@@ -2908,24 +3231,27 @@ var checkRails = ({
|
|
|
2908
3231
|
root,
|
|
2909
3232
|
userSettings
|
|
2910
3233
|
}) => {
|
|
2911
|
-
const
|
|
3234
|
+
const read = readGeonosisFile(root);
|
|
3235
|
+
if (read.kind === "unreadable") return [unreadableGeonosis("rails", read.error)];
|
|
3236
|
+
const declared = read.kind === "absent" ? void 0 : declaredEgress(read.config);
|
|
2912
3237
|
if (declared === void 0 || declared.allow.length === 0) {
|
|
2913
3238
|
return [
|
|
2914
|
-
|
|
2915
|
-
`${
|
|
3239
|
+
finding12(
|
|
3240
|
+
`${GEONOSIS_FILE} \u2192 rails.egress`,
|
|
2916
3241
|
"SKIP",
|
|
2917
3242
|
"this repo declares no egress allowlist, so there is no rendered setting for this to read back"
|
|
2918
|
-
)
|
|
3243
|
+
),
|
|
3244
|
+
...deniedEgress(root)
|
|
2919
3245
|
];
|
|
2920
3246
|
}
|
|
2921
3247
|
const sources = [
|
|
2922
3248
|
sourceAt(managedSettings, managedSettings),
|
|
2923
3249
|
sourceAt(userSettings, userSettings),
|
|
2924
|
-
sourceAt(
|
|
2925
|
-
sourceAt(
|
|
3250
|
+
sourceAt(join18(root, PROJECT), PROJECT),
|
|
3251
|
+
sourceAt(join18(root, LOCAL), LOCAL)
|
|
2926
3252
|
];
|
|
2927
3253
|
const unreadable = sources.filter((one) => one.unreadable).map(
|
|
2928
|
-
(one) =>
|
|
3254
|
+
(one) => finding12(
|
|
2929
3255
|
one.path,
|
|
2930
3256
|
"FAIL",
|
|
2931
3257
|
"is not readable JSON, and Claude Code SILENTLY ignores a settings file that fails validation \u2014 every setting rendered into this file loads as nothing, with no error anywhere. Fix the file, then render the allowlist again"
|
|
@@ -2940,93 +3266,22 @@ var checkRails = ({
|
|
|
2940
3266
|
return [
|
|
2941
3267
|
...unreadable,
|
|
2942
3268
|
...declared.allow.map(
|
|
2943
|
-
(domain) => effective.has(domain) ?
|
|
3269
|
+
(domain) => effective.has(domain) ? finding12(domain, "OK", "declared, and in the settings that actually load") : finding12(
|
|
2944
3270
|
domain,
|
|
2945
3271
|
"FAIL",
|
|
2946
|
-
`declared in ${
|
|
3272
|
+
`declared in ${GEONOSIS_FILE} \u2192 rails.egress and absent from every settings file that loads, so the run has no allowance for it.${why}`
|
|
2947
3273
|
)
|
|
2948
3274
|
),
|
|
2949
3275
|
...deniedEgress(root)
|
|
2950
3276
|
];
|
|
2951
3277
|
};
|
|
2952
3278
|
|
|
2953
|
-
// src/required-options.ts
|
|
2954
|
-
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2955
|
-
var HEADER = /requires option `([^`]+)`/;
|
|
2956
|
-
var optionsOf2 = (level) => Array.isArray(level) ? level.slice(1) : [];
|
|
2957
|
-
var missingOption = (error) => {
|
|
2958
|
-
const named2 = error.option;
|
|
2959
|
-
return typeof named2 === "string" ? named2 : HEADER.exec(String(error.message))?.[1];
|
|
2960
|
-
};
|
|
2961
|
-
var refusesBare = (rule) => {
|
|
2962
|
-
try {
|
|
2963
|
-
rule.create({ options: [], report: () => {
|
|
2964
|
-
} });
|
|
2965
|
-
return false;
|
|
2966
|
-
} catch (error) {
|
|
2967
|
-
return missingOption(error) !== void 0;
|
|
2968
|
-
}
|
|
2969
|
-
};
|
|
2970
|
-
var configured = (config, namespace) => [config.rules, ...config.overrides.map((one) => one.rules)].flatMap(
|
|
2971
|
-
(rules) => enabledRulesOf(rules, namespace).map((id) => [id, optionsOf2(rules[id])])
|
|
2972
|
-
);
|
|
2973
|
-
var checkRequiredOptions = async ({
|
|
2974
|
-
config,
|
|
2975
|
-
entry
|
|
2976
|
-
}) => {
|
|
2977
|
-
const loaded = await import(pathToFileURL2(entry).href).catch(() => void 0);
|
|
2978
|
-
const namespace = loaded?.default?.meta?.name;
|
|
2979
|
-
const rules = loaded?.default?.rules;
|
|
2980
|
-
if (typeof namespace !== "string" || namespace === "" || rules === void 0) return [];
|
|
2981
|
-
const enabled = configured(config, namespace);
|
|
2982
|
-
if (enabled.length === 0) return [];
|
|
2983
|
-
const missing = [];
|
|
2984
|
-
let gated = 0;
|
|
2985
|
-
for (const [id, options] of enabled) {
|
|
2986
|
-
const rule = rules[id.slice(namespace.length + 1)];
|
|
2987
|
-
if (rule === void 0) continue;
|
|
2988
|
-
if (refusesBare(rule)) gated += 1;
|
|
2989
|
-
try {
|
|
2990
|
-
rule.create({ options, report: () => {
|
|
2991
|
-
} });
|
|
2992
|
-
} catch (error) {
|
|
2993
|
-
const option = missingOption(error);
|
|
2994
|
-
if (option === void 0) continue;
|
|
2995
|
-
missing.push({
|
|
2996
|
-
check: "loaded",
|
|
2997
|
-
message: `it is enabled here with no \`${option}\`, and the rule refuses to be constructed without one \u2014 oxlint stops the whole run at load, linting nothing. Configure \`${option}\`, or turn the rule off`,
|
|
2998
|
-
subject: `${config.relative} \u2192 ${id}`,
|
|
2999
|
-
verdict: "FAIL"
|
|
3000
|
-
});
|
|
3001
|
-
}
|
|
3002
|
-
}
|
|
3003
|
-
if (missing.length > 0) return missing;
|
|
3004
|
-
return [
|
|
3005
|
-
{
|
|
3006
|
-
check: "loaded",
|
|
3007
|
-
message: `${gated} rules require options, ${gated} carry them`,
|
|
3008
|
-
subject: config.relative,
|
|
3009
|
-
verdict: "OK"
|
|
3010
|
-
}
|
|
3011
|
-
];
|
|
3012
|
-
};
|
|
3013
|
-
|
|
3014
3279
|
// src/seams.ts
|
|
3015
|
-
import {
|
|
3016
|
-
|
|
3017
|
-
var
|
|
3018
|
-
var
|
|
3019
|
-
|
|
3020
|
-
var declaredSeams = (root) => {
|
|
3021
|
-
const at = join17(root, CONFIG);
|
|
3022
|
-
if (!existsSync13(at)) return void 0;
|
|
3023
|
-
let parsed;
|
|
3024
|
-
try {
|
|
3025
|
-
parsed = JSON.parse(readFileSync16(at, "utf8"));
|
|
3026
|
-
} catch {
|
|
3027
|
-
return void 0;
|
|
3028
|
-
}
|
|
3029
|
-
const seams = parsed.adoption?.seams;
|
|
3280
|
+
import { globSync } from "fs";
|
|
3281
|
+
var finding13 = findingMaker("seams");
|
|
3282
|
+
var NO_SEAMS = `${GEONOSIS_FILE} declares no adoption.seams \u2014 nothing here says which files hold a floor's shape, so no bump can measure what it deleted; declare them when a floor is adopted`;
|
|
3283
|
+
var declaredSeams = (config) => {
|
|
3284
|
+
const seams = config.adoption?.seams;
|
|
3030
3285
|
return Array.isArray(seams) ? seams.filter((one) => typeof one === "string") : void 0;
|
|
3031
3286
|
};
|
|
3032
3287
|
var namesAFile = (root, glob) => globSync(glob, {
|
|
@@ -3034,25 +3289,32 @@ var namesAFile = (root, glob) => globSync(glob, {
|
|
|
3034
3289
|
exclude: (name) => name === "node_modules" || name.startsWith("node_modules/")
|
|
3035
3290
|
}).length > 0;
|
|
3036
3291
|
var checkSeams = ({ root }) => {
|
|
3037
|
-
const
|
|
3038
|
-
if (
|
|
3292
|
+
const read = readGeonosisFile(root);
|
|
3293
|
+
if (read.kind === "unreadable") return [unreadableGeonosis("seams", read.error)];
|
|
3294
|
+
const seams = read.kind === "absent" ? void 0 : declaredSeams(read.config);
|
|
3295
|
+
if (seams === void 0 || seams.length === 0) return [finding13(GEONOSIS_FILE, "SKIP", NO_SEAMS)];
|
|
3039
3296
|
const dead = seams.filter((glob) => !namesAFile(root, glob)).toSorted();
|
|
3040
3297
|
if (dead.length === 0) {
|
|
3041
3298
|
return [
|
|
3042
|
-
|
|
3299
|
+
finding13(
|
|
3300
|
+
GEONOSIS_FILE,
|
|
3301
|
+
"OK",
|
|
3302
|
+
`adoption.seams: ${seams.length} seam glob(s), every one names a file`
|
|
3303
|
+
)
|
|
3043
3304
|
];
|
|
3044
3305
|
}
|
|
3045
3306
|
return dead.map(
|
|
3046
|
-
(glob) =>
|
|
3307
|
+
(glob) => finding13(
|
|
3047
3308
|
glob,
|
|
3048
3309
|
"WARN",
|
|
3049
|
-
`names no file in this tree, so the seam measures nothing and every bump reads 0 deleted, 0 added over it. Next: take it out of adoption.seams in ${
|
|
3310
|
+
`names no file in this tree, so the seam measures nothing and every bump reads 0 deleted, 0 added over it. Next: take it out of adoption.seams in ${GEONOSIS_FILE}, or name the file that replaced it`
|
|
3050
3311
|
)
|
|
3051
3312
|
);
|
|
3052
3313
|
};
|
|
3053
3314
|
|
|
3054
3315
|
// src/doctor.ts
|
|
3055
3316
|
var exercisedOf = ({
|
|
3317
|
+
apparatus,
|
|
3056
3318
|
configs,
|
|
3057
3319
|
oxlint,
|
|
3058
3320
|
repoCorpus,
|
|
@@ -3088,8 +3350,14 @@ var exercisedOf = ({
|
|
|
3088
3350
|
root
|
|
3089
3351
|
}),
|
|
3090
3352
|
// #159: the rules whose engine this tree has not got. Beside the reach line rather than
|
|
3091
|
-
// inside it — one is about the corpus, the other about this repo's own manifests.
|
|
3092
|
-
|
|
3353
|
+
// inside it — one is about the corpus, the other about this repo's own manifests. A
|
|
3354
|
+
// config inside a corpus is evidence, not a product, and is not asked (row 585).
|
|
3355
|
+
checkEngines({
|
|
3356
|
+
config,
|
|
3357
|
+
entry,
|
|
3358
|
+
evidence: [corpus, ...repoCorpus === void 0 ? [] : [repoCorpus], ...apparatus],
|
|
3359
|
+
workspaces
|
|
3360
|
+
})
|
|
3093
3361
|
]);
|
|
3094
3362
|
return [reach, ...engines, ...checkPathGrants({ config, grants, root })];
|
|
3095
3363
|
})
|
|
@@ -3156,22 +3424,15 @@ var runDoctor = async ({
|
|
|
3156
3424
|
...await requiredOptionsOf(configs, root)
|
|
3157
3425
|
]
|
|
3158
3426
|
],
|
|
3159
|
-
[
|
|
3160
|
-
"group",
|
|
3161
|
-
() => {
|
|
3162
|
-
const declared = declaredGroupsOf(root);
|
|
3163
|
-
return checkGroup({
|
|
3164
|
-
...declared === void 0 ? {} : { groups: declared },
|
|
3165
|
-
root,
|
|
3166
|
-
workspaces
|
|
3167
|
-
});
|
|
3168
|
-
}
|
|
3169
|
-
],
|
|
3427
|
+
["group", () => checkGroup({ root, workspaces })],
|
|
3170
3428
|
[
|
|
3171
3429
|
"exercised",
|
|
3172
3430
|
() => {
|
|
3431
|
+
const read = readGeonosisFile(root);
|
|
3432
|
+
if (read.kind === "unreadable") return [unreadableGeonosis("exercised", read.error)];
|
|
3173
3433
|
const repoCorpus = repoCorpusOf(root);
|
|
3174
3434
|
return exercisedOf({
|
|
3435
|
+
apparatus: declaredApparatusOf(root),
|
|
3175
3436
|
configs,
|
|
3176
3437
|
oxlint: oxlint ?? resolveOxlint(root),
|
|
3177
3438
|
...repoCorpus === void 0 ? {} : { repoCorpus },
|
|
@@ -3186,9 +3447,10 @@ var runDoctor = async ({
|
|
|
3186
3447
|
["observability", () => checkObservability({ now: Date.now(), root })],
|
|
3187
3448
|
["drift", () => checkDrift({ root, workspaces })],
|
|
3188
3449
|
["deployed", () => checkDeployed({ root })],
|
|
3189
|
-
["rails", () => checkRails({ root, userSettings:
|
|
3450
|
+
["rails", () => checkRails({ root, userSettings: join19(home, USER_SETTINGS) })],
|
|
3190
3451
|
["exams", () => checkExams({ root, workspaces })],
|
|
3191
|
-
["seams", () => checkSeams({ root })]
|
|
3452
|
+
["seams", () => checkSeams({ root })],
|
|
3453
|
+
["formatter", () => checkFormatter({ root })]
|
|
3192
3454
|
];
|
|
3193
3455
|
const collected = [];
|
|
3194
3456
|
const ran = [];
|
|
@@ -3217,6 +3479,7 @@ var ABOUT = {
|
|
|
3217
3479
|
exams: "every adopted floor has a test file that runs its exam",
|
|
3218
3480
|
seams: "the seam globs adoption.seams declares each name a file",
|
|
3219
3481
|
exercised: "every enabled rule fires on at least one corpus file",
|
|
3482
|
+
formatter: "the formatter leaves the kit\u2019s installed rule files alone",
|
|
3220
3483
|
group: "the packages published on one version resolve to one version",
|
|
3221
3484
|
loaded: "the plugin oxlint would load is the one the manifest pins",
|
|
3222
3485
|
observability: "an exporter is configured, reachable, and something arrived through it lately",
|
|
@@ -3249,6 +3512,12 @@ var formatJson = (report) => `${JSON.stringify(report, null, 2)}
|
|
|
3249
3512
|
var ranOf = (findings) => new Set(findings.map((one) => one.check)).size;
|
|
3250
3513
|
|
|
3251
3514
|
export {
|
|
3515
|
+
CHECKS,
|
|
3516
|
+
DoctorError,
|
|
3517
|
+
GEONOSIS_FILE,
|
|
3518
|
+
APPARATUS_KEY,
|
|
3519
|
+
declaredApparatusOf,
|
|
3520
|
+
evidenceHolding,
|
|
3252
3521
|
CONFIG_FILE,
|
|
3253
3522
|
MANIFEST_FILE,
|
|
3254
3523
|
RATCHET_FILE,
|
|
@@ -3257,15 +3526,11 @@ export {
|
|
|
3257
3526
|
discoverConfigs,
|
|
3258
3527
|
discoverWorkspaces,
|
|
3259
3528
|
readRatchet,
|
|
3260
|
-
CHECKS,
|
|
3261
|
-
DoctorError,
|
|
3262
3529
|
defaultRef,
|
|
3263
3530
|
checkBaseline,
|
|
3264
3531
|
DEPLOYED_FILE,
|
|
3265
3532
|
NOT_WRITTEN,
|
|
3266
3533
|
checkDeployed,
|
|
3267
|
-
GEONOSIS_FILE,
|
|
3268
|
-
repoCorpusOf,
|
|
3269
3534
|
resolveFrom,
|
|
3270
3535
|
packageDirOf,
|
|
3271
3536
|
pluginVersionOf,
|
|
@@ -3279,6 +3544,7 @@ export {
|
|
|
3279
3544
|
FLOOR_PACKAGES,
|
|
3280
3545
|
READERS,
|
|
3281
3546
|
checkDrift,
|
|
3547
|
+
missingOptionOf,
|
|
3282
3548
|
enabledRulesOf,
|
|
3283
3549
|
checkExercised,
|
|
3284
3550
|
ENVELOPES_DIR,
|
|
@@ -3287,13 +3553,17 @@ export {
|
|
|
3287
3553
|
checkExams,
|
|
3288
3554
|
INSTALLED_PLUGINS,
|
|
3289
3555
|
PLUGIN_NAME,
|
|
3556
|
+
installedPlugins,
|
|
3290
3557
|
installedPluginVersions,
|
|
3558
|
+
loadedAt,
|
|
3291
3559
|
checkClaudePlugin,
|
|
3292
3560
|
SCOPE2 as SCOPE,
|
|
3293
3561
|
satisfies,
|
|
3294
3562
|
declaredFor,
|
|
3295
3563
|
checkLoaded,
|
|
3296
3564
|
checkObservability,
|
|
3565
|
+
repoCorpusOf,
|
|
3566
|
+
checkRequiredOptions,
|
|
3297
3567
|
checkRunner,
|
|
3298
3568
|
runDoctor,
|
|
3299
3569
|
formatDoctor,
|