@geonosis/doctor 1.4.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -18
- package/dist/{chunk-R4OLFNI6.js → chunk-R2R2OYH3.js} +1178 -358
- package/dist/doctor-cli.js +167 -9
- package/dist/index.d.ts +63 -6
- package/dist/index.js +13 -1
- package/package.json +5 -2
|
@@ -319,16 +319,34 @@ var checkDeployed = ({ root }) => {
|
|
|
319
319
|
];
|
|
320
320
|
};
|
|
321
321
|
|
|
322
|
+
// src/repo-corpus.ts
|
|
323
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
324
|
+
import { join as join3 } from "path";
|
|
325
|
+
var GEONOSIS_FILE = "geonosis.json";
|
|
326
|
+
var repoCorpusOf = (root) => {
|
|
327
|
+
const path = join3(root, GEONOSIS_FILE);
|
|
328
|
+
if (!existsSync2(path)) return void 0;
|
|
329
|
+
try {
|
|
330
|
+
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
331
|
+
const declared = parsed.doctor?.corpus;
|
|
332
|
+
return typeof declared === "string" && declared !== "" ? join3(root, declared) : void 0;
|
|
333
|
+
} catch {
|
|
334
|
+
return void 0;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
322
338
|
// src/types.ts
|
|
323
339
|
var CHECKS = [
|
|
324
340
|
"loaded",
|
|
341
|
+
"group",
|
|
325
342
|
"exercised",
|
|
326
343
|
"baseline",
|
|
327
344
|
"runner",
|
|
328
345
|
"envelope",
|
|
329
346
|
"drift",
|
|
330
347
|
"observability",
|
|
331
|
-
"deployed"
|
|
348
|
+
"deployed",
|
|
349
|
+
"rails"
|
|
332
350
|
];
|
|
333
351
|
var DoctorError = class extends Error {
|
|
334
352
|
constructor(message) {
|
|
@@ -338,9 +356,9 @@ var DoctorError = class extends Error {
|
|
|
338
356
|
};
|
|
339
357
|
|
|
340
358
|
// src/resolve.ts
|
|
341
|
-
import { existsSync as
|
|
359
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync } from "fs";
|
|
342
360
|
import { createRequire } from "module";
|
|
343
|
-
import { dirname, join as
|
|
361
|
+
import { dirname, join as join4 } from "path";
|
|
344
362
|
import { pathToFileURL } from "url";
|
|
345
363
|
var packageNameOf = (specifier) => {
|
|
346
364
|
const parts2 = specifier.split("/");
|
|
@@ -348,13 +366,13 @@ var packageNameOf = (specifier) => {
|
|
|
348
366
|
};
|
|
349
367
|
var resolveFrom = (dir, specifier) => {
|
|
350
368
|
if (specifier.startsWith(".") || specifier.startsWith("/")) {
|
|
351
|
-
return createRequire(
|
|
369
|
+
return createRequire(join4(dir, "noop.js")).resolve(specifier);
|
|
352
370
|
}
|
|
353
371
|
const name = packageNameOf(specifier);
|
|
354
372
|
let at = dir;
|
|
355
373
|
for (; ; ) {
|
|
356
|
-
if (
|
|
357
|
-
return createRequire(
|
|
374
|
+
if (existsSync3(join4(at, "node_modules", name, "package.json"))) {
|
|
375
|
+
return createRequire(join4(at, "noop.js")).resolve(specifier);
|
|
358
376
|
}
|
|
359
377
|
const parent = dirname(at);
|
|
360
378
|
if (parent === at) break;
|
|
@@ -367,10 +385,10 @@ var resolveFrom = (dir, specifier) => {
|
|
|
367
385
|
var packageDirOf = (entry, name) => {
|
|
368
386
|
let dir = dirname(entry);
|
|
369
387
|
for (; ; ) {
|
|
370
|
-
const manifest =
|
|
371
|
-
if (
|
|
388
|
+
const manifest = join4(dir, "package.json");
|
|
389
|
+
if (existsSync3(manifest)) {
|
|
372
390
|
try {
|
|
373
|
-
const parsed = JSON.parse(
|
|
391
|
+
const parsed = JSON.parse(readFileSync4(manifest, "utf8"));
|
|
374
392
|
if (parsed.name === name) return dir;
|
|
375
393
|
} catch {
|
|
376
394
|
}
|
|
@@ -405,7 +423,21 @@ var probesOf = async (entry, plugin) => {
|
|
|
405
423
|
})
|
|
406
424
|
);
|
|
407
425
|
};
|
|
408
|
-
var
|
|
426
|
+
var presumptionsOf = async (entry) => {
|
|
427
|
+
const loaded = await import(pathToFileURL(entry).href);
|
|
428
|
+
const namespace = loaded.default?.meta?.name;
|
|
429
|
+
return {
|
|
430
|
+
namespace: typeof namespace === "string" ? namespace : "",
|
|
431
|
+
presumed: Object.fromEntries(
|
|
432
|
+
Object.entries(loaded.default?.rules ?? {}).flatMap(([name, rule]) => {
|
|
433
|
+
const presumes = rule?.presumes;
|
|
434
|
+
const engine = presumes?.engine;
|
|
435
|
+
return typeof engine === "string" && Array.isArray(presumes?.packages) ? [[name, { engine, packages: presumes.packages }]] : [];
|
|
436
|
+
})
|
|
437
|
+
)
|
|
438
|
+
};
|
|
439
|
+
};
|
|
440
|
+
var corpusOfPlugin = (from, specifier) => join4(packageDirOf(resolveFrom(from, specifier), specifier), "corpus");
|
|
409
441
|
var real = (path) => {
|
|
410
442
|
try {
|
|
411
443
|
return realpathSync(path);
|
|
@@ -415,10 +447,281 @@ var real = (path) => {
|
|
|
415
447
|
};
|
|
416
448
|
var relativeToRoot = (root, path) => relativePath(real(root), real(path));
|
|
417
449
|
|
|
450
|
+
// src/group.ts
|
|
451
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
452
|
+
import { join as join6 } from "path";
|
|
453
|
+
|
|
454
|
+
// src/hooks.ts
|
|
455
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
|
|
456
|
+
import { join as join5 } from "path";
|
|
457
|
+
var HOOK_FILES = ["lefthook.yml", "lefthook.yaml", ".lefthook.yml", ".lefthook.yaml"];
|
|
458
|
+
var HOOK_DIRS = [".husky", ".githooks"];
|
|
459
|
+
var VERSION_MANAGED = /(?:^|[\s;&|(])(?<runner>pnpm|npx|bunx|yarn)\b(?:\s+(?:exec|run|dlx|x))?\s+(?<bin>geonosis(?:-[a-z-]+)?)\b/;
|
|
460
|
+
var NAMES_A_BIN = /(?:^|[\s;&|(/])geonosis(?:-[a-z-]+)?\b/;
|
|
461
|
+
var hookLines = (root) => {
|
|
462
|
+
const found = [];
|
|
463
|
+
const read = (path) => {
|
|
464
|
+
const at = relativePath(root, path);
|
|
465
|
+
for (const line of readFileSync5(path, "utf8").split("\n")) {
|
|
466
|
+
if (line.trim() !== "") found.push({ at, line });
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
for (const name of HOOK_FILES) {
|
|
470
|
+
const path = join5(root, name);
|
|
471
|
+
if (existsSync4(path)) read(path);
|
|
472
|
+
}
|
|
473
|
+
for (const name of HOOK_DIRS) {
|
|
474
|
+
const dir = join5(root, name);
|
|
475
|
+
if (!existsSync4(dir)) continue;
|
|
476
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
477
|
+
if (entry.isFile() && !entry.name.startsWith("_") && !entry.name.startsWith(".")) {
|
|
478
|
+
read(join5(dir, entry.name));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return found;
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
// src/group.ts
|
|
486
|
+
var FIXED_GROUP = [
|
|
487
|
+
"@geonosis/cli",
|
|
488
|
+
"@geonosis/doctor",
|
|
489
|
+
"@geonosis/evals",
|
|
490
|
+
"@geonosis/integrations",
|
|
491
|
+
"@geonosis/ledger",
|
|
492
|
+
"@geonosis/lint-parity",
|
|
493
|
+
"@geonosis/mcp",
|
|
494
|
+
"@geonosis/observability",
|
|
495
|
+
"@geonosis/oxlint-plugin-biological-architecture",
|
|
496
|
+
"@geonosis/policy",
|
|
497
|
+
"@geonosis/rails",
|
|
498
|
+
"@geonosis/ratchet",
|
|
499
|
+
"@geonosis/release",
|
|
500
|
+
"@geonosis/review",
|
|
501
|
+
"@geonosis/testbed",
|
|
502
|
+
"@geonosis/themekit",
|
|
503
|
+
"@geonosis/verify",
|
|
504
|
+
"@geonosis/verify-arch",
|
|
505
|
+
"@geonosis/visual-diff",
|
|
506
|
+
"@geonosis/walk",
|
|
507
|
+
"create-geonosis",
|
|
508
|
+
"geonosis"
|
|
509
|
+
];
|
|
510
|
+
var KIT_GROUP = { name: "@geonosis fixed group", packages: FIXED_GROUP };
|
|
511
|
+
var declaredGroupsOf = (root) => {
|
|
512
|
+
const path = join6(root, GEONOSIS_FILE);
|
|
513
|
+
if (!existsSync5(path)) return void 0;
|
|
514
|
+
let parsed;
|
|
515
|
+
try {
|
|
516
|
+
parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
517
|
+
} catch {
|
|
518
|
+
return void 0;
|
|
519
|
+
}
|
|
520
|
+
const declared = parsed.doctor?.groups;
|
|
521
|
+
if (declared === void 0) return void 0;
|
|
522
|
+
if (!Array.isArray(declared) || declared.length === 0) {
|
|
523
|
+
throw new DoctorError(
|
|
524
|
+
`${GEONOSIS_FILE} declares doctor.groups and names no group in it \u2014 say which packages must move together, or remove the key and get the kit's own group`
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
return declared.map((one, at) => {
|
|
528
|
+
const group = one;
|
|
529
|
+
if (typeof group.name !== "string" || !Array.isArray(group.packages) || group.packages.length < 2) {
|
|
530
|
+
throw new DoctorError(
|
|
531
|
+
`${GEONOSIS_FILE} doctor.groups[${at}] needs a name and at least two packages \u2014 one package cannot disagree with itself`
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
return { name: group.name, packages: group.packages };
|
|
535
|
+
});
|
|
536
|
+
};
|
|
537
|
+
var labelOf = (workspace) => workspace.relative === "" ? "root" : workspace.relative;
|
|
538
|
+
var versionOf = (dir, name) => {
|
|
539
|
+
let entry;
|
|
540
|
+
try {
|
|
541
|
+
entry = resolveFrom(dir, name);
|
|
542
|
+
} catch {
|
|
543
|
+
return { kind: "absent" };
|
|
544
|
+
}
|
|
545
|
+
try {
|
|
546
|
+
const manifest = JSON.parse(
|
|
547
|
+
readFileSync6(join6(packageDirOf(entry, name), "package.json"), "utf8")
|
|
548
|
+
);
|
|
549
|
+
return typeof manifest.version === "string" ? { kind: "read", version: manifest.version } : { kind: "unreadable", why: "its package.json declares no version" };
|
|
550
|
+
} catch (error) {
|
|
551
|
+
return { kind: "unreadable", why: String(error.message).split("\n")[0] ?? "" };
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
var finding3 = (subject, verdict, message) => ({
|
|
555
|
+
check: "group",
|
|
556
|
+
message,
|
|
557
|
+
subject,
|
|
558
|
+
verdict
|
|
559
|
+
});
|
|
560
|
+
var said = (versions) => [...versions.entries()].map(([version, from]) => `${version} (${from.join(", ")})`).join("; ");
|
|
561
|
+
var repoWide = (name, found) => {
|
|
562
|
+
const versions = /* @__PURE__ */ new Map();
|
|
563
|
+
for (const one of found) versions.set(one.version, [...versions.get(one.version) ?? [], one.at]);
|
|
564
|
+
if (versions.size === 1) return void 0;
|
|
565
|
+
return finding3(
|
|
566
|
+
name,
|
|
567
|
+
"FAIL",
|
|
568
|
+
`${versions.size} versions in one tree \u2014 ${said(versions)}. They are published as one number, so a tree holding two is a tree where one workspace is running code another has already replaced; remove the nested copies and reinstall`
|
|
569
|
+
);
|
|
570
|
+
};
|
|
571
|
+
var perWorkspace = (group, at, found) => {
|
|
572
|
+
if (new Set(found.map((one) => one.version)).size === 1) return void 0;
|
|
573
|
+
const listed = found.map((one) => `${one.name} ${one.version}`).join(", ");
|
|
574
|
+
return finding3(
|
|
575
|
+
at,
|
|
576
|
+
"FAIL",
|
|
577
|
+
`${group.name} does not hold together here \u2014 ${listed}. Every member is cut on one version, so a workspace resolving two of them is a bump that landed halfway`
|
|
578
|
+
);
|
|
579
|
+
};
|
|
580
|
+
var DOORS = ["@geonosis/cli", "geonosis"];
|
|
581
|
+
var manifestOf = (dir, name) => {
|
|
582
|
+
try {
|
|
583
|
+
return JSON.parse(
|
|
584
|
+
readFileSync6(join6(packageDirOf(resolveFrom(dir, name), name), "package.json"), "utf8")
|
|
585
|
+
);
|
|
586
|
+
} catch {
|
|
587
|
+
return void 0;
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
var binNamesOf = (manifest, name) => {
|
|
591
|
+
const declared = manifest?.bin;
|
|
592
|
+
return typeof declared === "string" ? [name.replace(/^@[^/]+\//, "")] : Object.keys(declared ?? {});
|
|
593
|
+
};
|
|
594
|
+
var declaredIn2 = (manifest) => [
|
|
595
|
+
...new Set(
|
|
596
|
+
[
|
|
597
|
+
manifest.dependencies,
|
|
598
|
+
manifest.devDependencies,
|
|
599
|
+
manifest.optionalDependencies,
|
|
600
|
+
manifest.peerDependencies
|
|
601
|
+
].flatMap((block) => Object.keys(block ?? {}))
|
|
602
|
+
)
|
|
603
|
+
];
|
|
604
|
+
var calls = (lines, bin) => new RegExp(`(?:^|[\\s;&|(/])${bin}(?:\\s|$)`).test(lines);
|
|
605
|
+
var workspaceCaller = (workspaces, bins) => {
|
|
606
|
+
for (const workspace of workspaces) {
|
|
607
|
+
if (workspace.relative === "") continue;
|
|
608
|
+
for (const [script, line] of Object.entries(workspace.manifest.scripts ?? {})) {
|
|
609
|
+
if (bins.some((bin) => calls(line, bin))) return { at: workspace.relative, script };
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return void 0;
|
|
613
|
+
};
|
|
614
|
+
var rootDeclarations = (root, workspaces) => {
|
|
615
|
+
const here = workspaces.find((one) => one.relative === "");
|
|
616
|
+
if (here === void 0) return [];
|
|
617
|
+
const declared = declaredIn2(here.manifest);
|
|
618
|
+
const door = DOORS.find((name) => declared.includes(name));
|
|
619
|
+
if (door === void 0) return [];
|
|
620
|
+
const owned = new Set(
|
|
621
|
+
[door, ...DOORS].flatMap((name) => {
|
|
622
|
+
const manifest = manifestOf(root, name);
|
|
623
|
+
return manifest === void 0 ? [] : Object.keys(manifest.dependencies ?? {});
|
|
624
|
+
})
|
|
625
|
+
);
|
|
626
|
+
const callers = [
|
|
627
|
+
...Object.values(here.manifest.scripts ?? {}),
|
|
628
|
+
...hookLines(root).map((one) => one.line)
|
|
629
|
+
].join("\n");
|
|
630
|
+
const loadedHere = new Set(readConfig(join6(root, CONFIG_FILE), root).jsPlugins);
|
|
631
|
+
const spare = declared.filter(
|
|
632
|
+
(name) => !DOORS.includes(name) && owned.has(name) && !loadedHere.has(name) && !binNamesOf(manifestOf(root, name), name).some((bin) => calls(callers, bin))
|
|
633
|
+
);
|
|
634
|
+
if (spare.length === 0) {
|
|
635
|
+
return [
|
|
636
|
+
finding3(
|
|
637
|
+
"package.json",
|
|
638
|
+
"OK",
|
|
639
|
+
`every @geonosis package this root declares is ${door} or a bin its own scripts call by name`
|
|
640
|
+
)
|
|
641
|
+
];
|
|
642
|
+
}
|
|
643
|
+
return spare.map((name) => {
|
|
644
|
+
const bins = binNamesOf(manifestOf(root, name), name);
|
|
645
|
+
const inWorkspace = workspaceCaller(workspaces, bins);
|
|
646
|
+
if (inWorkspace !== void 0) {
|
|
647
|
+
return finding3(
|
|
648
|
+
name,
|
|
649
|
+
"OK",
|
|
650
|
+
`no root script calls its bin, and ${inWorkspace.at}'s "${inWorkspace.script}" script does \u2014 under a hoisted linker the ROOT declaration is what makes that bin resolvable there, so this is the declaration doing its job`
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
return finding3(
|
|
654
|
+
name,
|
|
655
|
+
"WARN",
|
|
656
|
+
`the root declares it and no script in this repo calls its bin \u2014 ${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`
|
|
657
|
+
);
|
|
658
|
+
});
|
|
659
|
+
};
|
|
660
|
+
var checkGroup = ({
|
|
661
|
+
groups,
|
|
662
|
+
root,
|
|
663
|
+
workspaces
|
|
664
|
+
}) => {
|
|
665
|
+
const findings = [...rootDeclarations(root, workspaces)];
|
|
666
|
+
for (const group of groups ?? [KIT_GROUP]) {
|
|
667
|
+
const found = [];
|
|
668
|
+
for (const workspace of workspaces) {
|
|
669
|
+
const at = labelOf(workspace);
|
|
670
|
+
const here = [];
|
|
671
|
+
for (const name of group.packages) {
|
|
672
|
+
const reading = versionOf(workspace.dir, name);
|
|
673
|
+
if (reading.kind === "read") here.push({ at, name, version: reading.version });
|
|
674
|
+
if (reading.kind === "unreadable") {
|
|
675
|
+
findings.push(
|
|
676
|
+
finding3(
|
|
677
|
+
`${at} \u2192 ${name}`,
|
|
678
|
+
"UNJUDGED",
|
|
679
|
+
`it resolves from here and ${reading.why} \u2014 this copy could be any version, so the group cannot be said to hold or not to hold`
|
|
680
|
+
)
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const split2 = here.length > 0 ? perWorkspace(group, at, here) : void 0;
|
|
685
|
+
if (split2 !== void 0) findings.push(split2);
|
|
686
|
+
found.push(...here);
|
|
687
|
+
}
|
|
688
|
+
if (found.length === 0) {
|
|
689
|
+
findings.push(
|
|
690
|
+
finding3(
|
|
691
|
+
group.name,
|
|
692
|
+
"SKIP",
|
|
693
|
+
`no workspace under ${root} resolves any of ${group.packages.join(", ")}, so nothing here versions together`
|
|
694
|
+
)
|
|
695
|
+
);
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
const names = [...new Set(found.map((one) => one.name))].toSorted();
|
|
699
|
+
const split = names.flatMap((name) => {
|
|
700
|
+
const line = repoWide(
|
|
701
|
+
name,
|
|
702
|
+
found.filter((one) => one.name === name)
|
|
703
|
+
);
|
|
704
|
+
return line === void 0 ? [] : [line];
|
|
705
|
+
});
|
|
706
|
+
findings.push(...split);
|
|
707
|
+
if (split.length === 0) {
|
|
708
|
+
const versions = [...new Set(found.map((one) => one.version))];
|
|
709
|
+
findings.push(
|
|
710
|
+
finding3(
|
|
711
|
+
group.name,
|
|
712
|
+
"OK",
|
|
713
|
+
`${names.length} of ${group.packages.length} installed, at ${versions.join(", ")}, one copy each across ${new Set(found.map((one) => one.at)).size} workspaces`
|
|
714
|
+
)
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return findings;
|
|
719
|
+
};
|
|
720
|
+
|
|
418
721
|
// src/drift.ts
|
|
419
|
-
import { closeSync, existsSync as
|
|
722
|
+
import { closeSync, existsSync as existsSync6, openSync, readdirSync as readdirSync3, readFileSync as readFileSync7, readSync } from "fs";
|
|
420
723
|
import { homedir } from "os";
|
|
421
|
-
import { join as
|
|
724
|
+
import { join as join7, sep as sep2 } from "path";
|
|
422
725
|
|
|
423
726
|
// src/overrides.ts
|
|
424
727
|
var SPECIAL = /* @__PURE__ */ new Set(["$", "(", ")", "+", ".", "/", "@", "\\", "^", "|"]);
|
|
@@ -490,7 +793,7 @@ var GEONOSIS = "geonosis.json";
|
|
|
490
793
|
var LAW = "CLAUDE.md";
|
|
491
794
|
var CEILING = 200;
|
|
492
795
|
var SWITCHED_OFF = /^\s*if:\s*(?:\$\{\{\s*)?false\b/m;
|
|
493
|
-
var
|
|
796
|
+
var finding4 = (subject, verdict, message) => ({
|
|
494
797
|
check: "drift",
|
|
495
798
|
message,
|
|
496
799
|
subject,
|
|
@@ -502,33 +805,33 @@ var filesUnder = (dir, match) => {
|
|
|
502
805
|
const walk2 = (at) => {
|
|
503
806
|
let entries;
|
|
504
807
|
try {
|
|
505
|
-
entries =
|
|
808
|
+
entries = readdirSync3(at, { withFileTypes: true });
|
|
506
809
|
} catch {
|
|
507
810
|
return;
|
|
508
811
|
}
|
|
509
812
|
for (const entry of entries) {
|
|
510
813
|
if (entry.isDirectory()) {
|
|
511
|
-
if (!entry.name.startsWith(".") && !NEVER_WALKED2.has(entry.name)) walk2(
|
|
814
|
+
if (!entry.name.startsWith(".") && !NEVER_WALKED2.has(entry.name)) walk2(join7(at, entry.name));
|
|
512
815
|
continue;
|
|
513
816
|
}
|
|
514
|
-
if (entry.isFile() && match(entry.name)) found.push(
|
|
817
|
+
if (entry.isFile() && match(entry.name)) found.push(join7(at, entry.name));
|
|
515
818
|
}
|
|
516
819
|
};
|
|
517
820
|
walk2(dir);
|
|
518
821
|
return found;
|
|
519
822
|
};
|
|
520
823
|
var ci = (root) => {
|
|
521
|
-
const dir =
|
|
522
|
-
if (!
|
|
523
|
-
return [
|
|
824
|
+
const dir = join7(root, WORKFLOWS);
|
|
825
|
+
if (!existsSync6(dir)) {
|
|
826
|
+
return [finding4(WORKFLOWS, "SKIP", "there are no workflows here to read")];
|
|
524
827
|
}
|
|
525
828
|
return filesUnder(dir, (name) => name.endsWith(".yml") || name.endsWith(".yaml")).map((path) => {
|
|
526
829
|
const at = relativePath(root, path);
|
|
527
|
-
return SWITCHED_OFF.test(
|
|
830
|
+
return SWITCHED_OFF.test(readFileSync7(path, "utf8")) ? finding4(
|
|
528
831
|
at,
|
|
529
832
|
"FAIL",
|
|
530
833
|
"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"
|
|
531
|
-
) :
|
|
834
|
+
) : finding4(at, "OK", "nothing in it is switched off");
|
|
532
835
|
});
|
|
533
836
|
};
|
|
534
837
|
var holds = (parent, child) => child === parent || child.startsWith(`${parent}${sep2}`);
|
|
@@ -544,10 +847,10 @@ var orphanTests = (root, workspaces) => {
|
|
|
544
847
|
orphaned.set(at, [...orphaned.get(at) ?? [], relativePath(root, path)]);
|
|
545
848
|
}
|
|
546
849
|
if (orphaned.size === 0) {
|
|
547
|
-
return [
|
|
850
|
+
return [finding4("test files", "OK", "every test file sits under a workspace that runs tests")];
|
|
548
851
|
}
|
|
549
852
|
return [...orphaned.entries()].map(
|
|
550
|
-
([at, files]) =>
|
|
853
|
+
([at, files]) => finding4(
|
|
551
854
|
at,
|
|
552
855
|
"FAIL",
|
|
553
856
|
`${files.length} test file(s) here and no test script to run them \u2014 ${files.slice(0, 3).join(", ")}`
|
|
@@ -572,16 +875,16 @@ var pathsRunByScript = (script) => script.split(BETWEEN_COMMANDS).map(pathRunBy)
|
|
|
572
875
|
var scriptPaths = (workspaces) => {
|
|
573
876
|
const missing = workspaces.flatMap(
|
|
574
877
|
(one) => Object.entries(one.manifest.scripts ?? {}).flatMap(
|
|
575
|
-
([name, script]) => pathsRunByScript(script).filter((path) => !
|
|
878
|
+
([name, script]) => pathsRunByScript(script).filter((path) => !existsSync6(join7(one.dir, path))).map((path) => ({ name, path, workspace: one }))
|
|
576
879
|
)
|
|
577
880
|
);
|
|
578
881
|
if (missing.length === 0) {
|
|
579
882
|
return [
|
|
580
|
-
|
|
883
|
+
finding4("script paths", "OK", "every file a script hands to bun, node or tsx is on disk")
|
|
581
884
|
];
|
|
582
885
|
}
|
|
583
886
|
return missing.map(
|
|
584
|
-
({ name, path, workspace }) =>
|
|
887
|
+
({ name, path, workspace }) => finding4(
|
|
585
888
|
workspace.relative === "" ? "package.json" : `${workspace.relative}/package.json`,
|
|
586
889
|
"FAIL",
|
|
587
890
|
`the "${name}" script runs ${path} and there is no such file \u2014 this script fails at that command, and every gate that calls it has been running nothing since the file went`
|
|
@@ -604,16 +907,16 @@ var workspaceBins = (workspaces) => {
|
|
|
604
907
|
var linkedBins = (root, workspaces) => {
|
|
605
908
|
const bins = workspaceBins(workspaces);
|
|
606
909
|
if (bins.size === 0) {
|
|
607
|
-
return [
|
|
910
|
+
return [finding4("workspace bins", "SKIP", "no workspace here declares a bin")];
|
|
608
911
|
}
|
|
609
912
|
const missing = workspaces.flatMap(
|
|
610
913
|
(one) => Object.entries(one.manifest.scripts ?? {}).flatMap(
|
|
611
|
-
([script, body]) => [...new Set(body.split(/[\s;|&()]+/).filter((word) => bins.has(word)))].filter((name) => !
|
|
914
|
+
([script, body]) => [...new Set(body.split(/[\s;|&()]+/).filter((word) => bins.has(word)))].filter((name) => !existsSync6(join7(root, "node_modules/.bin", name))).map((name) => ({ name, script, workspace: one }))
|
|
612
915
|
)
|
|
613
916
|
);
|
|
614
917
|
if (missing.length === 0) {
|
|
615
918
|
return [
|
|
616
|
-
|
|
919
|
+
finding4(
|
|
617
920
|
"workspace bins",
|
|
618
921
|
"OK",
|
|
619
922
|
"every workspace bin a script names is linked under the root"
|
|
@@ -621,7 +924,7 @@ var linkedBins = (root, workspaces) => {
|
|
|
621
924
|
];
|
|
622
925
|
}
|
|
623
926
|
return missing.map(
|
|
624
|
-
({ name, script, workspace }) =>
|
|
927
|
+
({ name, script, workspace }) => finding4(
|
|
625
928
|
name,
|
|
626
929
|
"FAIL",
|
|
627
930
|
`the "${script}" script in ${workspace.relative === "" ? "package.json" : `${workspace.relative}/package.json`} calls it, ${bins.get(name)?.manifest.name ?? "a workspace"} declares it, and node_modules/.bin/${name} is not there \u2014 add that package as a dependency of the one whose script calls it, so the package manager links the bin`
|
|
@@ -643,7 +946,7 @@ var preloadAroundPnpm = (root, workspaces) => {
|
|
|
643
946
|
const some = scripts.slice(0, 3).join(", ");
|
|
644
947
|
const rest = scripts.length > 3 ? ` and ${scripts.length - 3} more` : "";
|
|
645
948
|
return [
|
|
646
|
-
|
|
949
|
+
finding4(
|
|
647
950
|
"NODE_OPTIONS",
|
|
648
951
|
"WARN",
|
|
649
952
|
`${scripts.length} script(s) preload something through NODE_OPTIONS (${some}${rest}), and the "${named2}" counter(s) shell out to pnpm \u2014 a nested pnpm INHERITS the option and dies on it (a preloaded resolver patch sends it looking for a .pnpmfile.mjs that is not there). Run the ratchet outside those scripts, or unset NODE_OPTIONS for the nested call: env -u NODE_OPTIONS pnpm \u2026`
|
|
@@ -679,11 +982,11 @@ var everythingRun = (root, workspaces) => {
|
|
|
679
982
|
const scripts = workspaces.flatMap((one) => Object.values(one.manifest.scripts ?? {}));
|
|
680
983
|
const counters = (readRatchet(root)?.counters ?? []).map((entry) => String(entry.command ?? ""));
|
|
681
984
|
const workflows = filesUnder(
|
|
682
|
-
|
|
985
|
+
join7(root, WORKFLOWS),
|
|
683
986
|
(name) => name.endsWith(".yml") || name.endsWith(".yaml")
|
|
684
987
|
).map((path) => {
|
|
685
988
|
try {
|
|
686
|
-
return
|
|
989
|
+
return readFileSync7(path, "utf8");
|
|
687
990
|
} catch {
|
|
688
991
|
return "";
|
|
689
992
|
}
|
|
@@ -694,7 +997,7 @@ var generatedFiles = (root, workspaces) => {
|
|
|
694
997
|
const marked = filesUnder(root, (name) => CAN_CARRY_A_MARKER.test(name)).map((path) => ({ marker: markerIn(path), path })).filter((one) => one.marker !== void 0);
|
|
695
998
|
if (marked.length === 0) {
|
|
696
999
|
return [
|
|
697
|
-
|
|
1000
|
+
finding4(
|
|
698
1001
|
"generated files",
|
|
699
1002
|
"SKIP",
|
|
700
1003
|
"nothing here says it was generated, so there is no write half here to look for a read half of"
|
|
@@ -705,13 +1008,13 @@ var generatedFiles = (root, workspaces) => {
|
|
|
705
1008
|
return marked.map(({ marker, path }) => {
|
|
706
1009
|
const at = relativePath(root, path);
|
|
707
1010
|
if (!("gate" in marker)) {
|
|
708
|
-
return
|
|
1011
|
+
return finding4(
|
|
709
1012
|
at,
|
|
710
1013
|
"FAIL",
|
|
711
1014
|
`generated by \`${marker.writer}\` and it names no gate that reads it back. 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. Re-run \`${marker.writer}\` on a version that writes the \`geonosis:gated-by:\` marker, and run the gate it names`
|
|
712
1015
|
);
|
|
713
1016
|
}
|
|
714
|
-
return run.includes(marker.gate) ?
|
|
1017
|
+
return run.includes(marker.gate) ? finding4(at, "OK", `generated, and \`${marker.gate}\` reads it back`) : finding4(
|
|
715
1018
|
at,
|
|
716
1019
|
"FAIL",
|
|
717
1020
|
`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`
|
|
@@ -719,10 +1022,10 @@ var generatedFiles = (root, workspaces) => {
|
|
|
719
1022
|
});
|
|
720
1023
|
};
|
|
721
1024
|
var readGeonosis = (root) => {
|
|
722
|
-
const path =
|
|
723
|
-
if (!
|
|
1025
|
+
const path = join7(root, GEONOSIS);
|
|
1026
|
+
if (!existsSync6(path)) return void 0;
|
|
724
1027
|
try {
|
|
725
|
-
return JSON.parse(
|
|
1028
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
726
1029
|
} catch {
|
|
727
1030
|
return void 0;
|
|
728
1031
|
}
|
|
@@ -730,15 +1033,15 @@ var readGeonosis = (root) => {
|
|
|
730
1033
|
var law = (root, config) => {
|
|
731
1034
|
const declared = config?.law ?? {};
|
|
732
1035
|
const file = typeof declared.file === "string" ? declared.file : LAW;
|
|
733
|
-
const path =
|
|
734
|
-
if (!
|
|
735
|
-
return [
|
|
1036
|
+
const path = join7(root, file);
|
|
1037
|
+
if (!existsSync6(path)) {
|
|
1038
|
+
return [finding4(file, "SKIP", "there is no law file here to measure")];
|
|
736
1039
|
}
|
|
737
|
-
const source =
|
|
1040
|
+
const source = readFileSync7(path, "utf8");
|
|
738
1041
|
const lines = source.split("\n").length - (source.endsWith("\n") ? 1 : 0);
|
|
739
1042
|
if (typeof declared.maxLines !== "number") {
|
|
740
1043
|
return [
|
|
741
|
-
|
|
1044
|
+
finding4(
|
|
742
1045
|
file,
|
|
743
1046
|
"SKIP",
|
|
744
1047
|
`${lines} lines, and no ceiling to measure them against \u2014 set law.maxLines in geonosis.json to have this asked (${CEILING} is what both source repos converged on)`
|
|
@@ -747,18 +1050,18 @@ var law = (root, config) => {
|
|
|
747
1050
|
}
|
|
748
1051
|
const ceiling = declared.maxLines;
|
|
749
1052
|
return [
|
|
750
|
-
lines > ceiling ?
|
|
1053
|
+
lines > ceiling ? finding4(
|
|
751
1054
|
file,
|
|
752
1055
|
"WARN",
|
|
753
1056
|
`${lines} lines against a ceiling of ${ceiling} \u2014 depth belongs in the skills and in the rules, where it is read`
|
|
754
|
-
) :
|
|
1057
|
+
) : finding4(file, "OK", `${lines} lines, under the ceiling of ${ceiling}`)
|
|
755
1058
|
];
|
|
756
1059
|
};
|
|
757
1060
|
var KIT_PLUGIN = "geonosis";
|
|
758
1061
|
var enablesKit = (path) => {
|
|
759
|
-
if (!
|
|
1062
|
+
if (!existsSync6(path)) return false;
|
|
760
1063
|
try {
|
|
761
|
-
const parsed = JSON.parse(
|
|
1064
|
+
const parsed = JSON.parse(readFileSync7(path, "utf8"));
|
|
762
1065
|
const enabled = parsed.enabledPlugins;
|
|
763
1066
|
if (typeof enabled !== "object" || enabled === null) return false;
|
|
764
1067
|
return Object.entries(enabled).some(
|
|
@@ -769,13 +1072,13 @@ var enablesKit = (path) => {
|
|
|
769
1072
|
}
|
|
770
1073
|
};
|
|
771
1074
|
var hooks = (root, userSettings) => {
|
|
772
|
-
const path =
|
|
1075
|
+
const path = join7(root, SETTINGS);
|
|
773
1076
|
if (enablesKit(path)) {
|
|
774
|
-
return [
|
|
1077
|
+
return [finding4(SETTINGS, "OK", `\`enabledPlugins\` here enables the kit\u2019s plugin`)];
|
|
775
1078
|
}
|
|
776
1079
|
if (enablesKit(userSettings)) {
|
|
777
1080
|
return [
|
|
778
|
-
|
|
1081
|
+
finding4(
|
|
779
1082
|
SETTINGS,
|
|
780
1083
|
"OK",
|
|
781
1084
|
`installed elsewhere \u2014 not this repo\u2019s to declare: \`enabledPlugins\` in ${userSettings} enables it for every repo on this machine`
|
|
@@ -783,13 +1086,167 @@ var hooks = (root, userSettings) => {
|
|
|
783
1086
|
];
|
|
784
1087
|
}
|
|
785
1088
|
return [
|
|
786
|
-
|
|
1089
|
+
finding4(
|
|
787
1090
|
SETTINGS,
|
|
788
1091
|
"WARN",
|
|
789
1092
|
`nothing enables the kit\u2019s plugin, so none of the hooks, agents or skills reach this repo \u2014 the gates run, the method does not. What satisfies this: a "geonosis@<marketplace>" entry under \`enabledPlugins\` in ${SETTINGS} here, or the same in the user-scope ${userSettings}`
|
|
790
1093
|
)
|
|
791
1094
|
];
|
|
792
1095
|
};
|
|
1096
|
+
var ALLOW_BUILDS_KEY = "allowBuilds";
|
|
1097
|
+
var allowedBuilds = (root) => {
|
|
1098
|
+
const path = join7(root, WORKSPACE_YAML);
|
|
1099
|
+
if (!existsSync6(path)) return void 0;
|
|
1100
|
+
const lines = readFileSync7(path, "utf8").split("\n");
|
|
1101
|
+
const at = lines.findIndex((line) => new RegExp(`^${ALLOW_BUILDS_KEY}\\s*:`).test(line));
|
|
1102
|
+
if (at < 0) return void 0;
|
|
1103
|
+
const names = [];
|
|
1104
|
+
for (const line of lines.slice(at + 1)) {
|
|
1105
|
+
if (/^\s*(?:#.*)?$/.test(line)) continue;
|
|
1106
|
+
const entry = /^\s+(?<name>'[^']+'|"[^"]+"|[^:\s]+)\s*:/.exec(line);
|
|
1107
|
+
const name = entry?.groups?.["name"];
|
|
1108
|
+
if (name === void 0) break;
|
|
1109
|
+
names.push(name.replace(/^['"]|['"]$/g, ""));
|
|
1110
|
+
}
|
|
1111
|
+
return names;
|
|
1112
|
+
};
|
|
1113
|
+
var PLATFORM = /(?:^|[-/])(?:darwin|linux|win32|windows|freebsd|openbsd|android|sunos)(?:[-.]|$)|(?:^|[-/])(?:arm64|x64|ia32|ppc64|s390x|riscv64)(?:[-.]|$)/;
|
|
1114
|
+
var familyOf = (name) => name.split("/").slice(0, -1).join("/") || name;
|
|
1115
|
+
var platformSplitBuilds = (root) => {
|
|
1116
|
+
const names = allowedBuilds(root);
|
|
1117
|
+
if (names === void 0 || names.length === 0) return [];
|
|
1118
|
+
const families = /* @__PURE__ */ new Map();
|
|
1119
|
+
for (const name of names.filter((one) => PLATFORM.test(one))) {
|
|
1120
|
+
const family = familyOf(name);
|
|
1121
|
+
families.set(family, [...families.get(family) ?? [], name]);
|
|
1122
|
+
}
|
|
1123
|
+
return [...families.entries()].flatMap(
|
|
1124
|
+
([family, allowed]) => allowed.length > 1 ? [] : [
|
|
1125
|
+
finding4(
|
|
1126
|
+
allowed[0] ?? family,
|
|
1127
|
+
"WARN",
|
|
1128
|
+
`this ${ALLOW_BUILDS_KEY} entry names one platform of ${family}, which publishes one package per platform \u2014 pnpm writes the entry for the machine that ran the install, so an install on any other platform refuses by the name that is missing, on somebody else's machine. List every platform ${family} publishes, or none of them`
|
|
1129
|
+
)
|
|
1130
|
+
]
|
|
1131
|
+
);
|
|
1132
|
+
};
|
|
1133
|
+
var LINTS_STAGED = /\b(?:oxlint|eslint|biome)\b[^\n]*(?:\{staged_files\}|\{files\}|\$\{?[@*]|"\$@")/;
|
|
1134
|
+
var TAKES_A_VALUE = /* @__PURE__ */ new Set([
|
|
1135
|
+
"--config",
|
|
1136
|
+
"--format",
|
|
1137
|
+
"--ignore-path",
|
|
1138
|
+
"--max-warnings",
|
|
1139
|
+
"--reporter",
|
|
1140
|
+
"--rulesdir",
|
|
1141
|
+
"--tsconfig",
|
|
1142
|
+
"-c"
|
|
1143
|
+
]);
|
|
1144
|
+
var pathsLintedBy = (script, dir) => {
|
|
1145
|
+
const words = script.split(/\s+/).filter((word) => word !== "");
|
|
1146
|
+
const named2 = [];
|
|
1147
|
+
for (const [at, word] of words.entries()) {
|
|
1148
|
+
if (word.startsWith("-")) continue;
|
|
1149
|
+
const before = words[at - 1] ?? "";
|
|
1150
|
+
if (TAKES_A_VALUE.has(before)) continue;
|
|
1151
|
+
if (existsSync6(join7(dir, word))) named2.push(word.replace(/^\.\//, "").replace(/\/$/, ""));
|
|
1152
|
+
}
|
|
1153
|
+
return named2;
|
|
1154
|
+
};
|
|
1155
|
+
var LINTABLE = /\.[cm]?[jt]sx?$/;
|
|
1156
|
+
var sourceDirsIn = (dir) => {
|
|
1157
|
+
let entries;
|
|
1158
|
+
try {
|
|
1159
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
1160
|
+
} catch {
|
|
1161
|
+
return [];
|
|
1162
|
+
}
|
|
1163
|
+
return entries.filter(
|
|
1164
|
+
(entry) => entry.isDirectory() && !entry.name.startsWith(".") && !NEVER_WALKED2.has(entry.name) && filesUnder(join7(dir, entry.name), (name) => LINTABLE.test(name)).length > 0
|
|
1165
|
+
).map((entry) => entry.name);
|
|
1166
|
+
};
|
|
1167
|
+
var lintScopes = (root, workspaces) => {
|
|
1168
|
+
if (!hookLines(root).some((one) => LINTS_STAGED.test(one.line))) {
|
|
1169
|
+
return [
|
|
1170
|
+
finding4(
|
|
1171
|
+
"lint scope",
|
|
1172
|
+
"SKIP",
|
|
1173
|
+
"no committed hook lints staged files here, so there is no second file set for a lint script to be narrower than"
|
|
1174
|
+
)
|
|
1175
|
+
];
|
|
1176
|
+
}
|
|
1177
|
+
return workspaces.flatMap((workspace) => {
|
|
1178
|
+
const script = workspace.manifest.scripts?.lint;
|
|
1179
|
+
if (typeof script !== "string" || script.trim() === "") return [];
|
|
1180
|
+
const at = workspace.relative === "" ? "package.json" : `${workspace.relative}/package.json`;
|
|
1181
|
+
const covered = pathsLintedBy(script, workspace.dir);
|
|
1182
|
+
if (covered.length === 0 || covered.includes(".")) {
|
|
1183
|
+
return [finding4(at, "OK", `"${script}" covers everything the hook could stage here`)];
|
|
1184
|
+
}
|
|
1185
|
+
const uncovered = sourceDirsIn(workspace.dir).filter(
|
|
1186
|
+
(name) => !covered.some((path) => name === path || path.startsWith(`${name}/`))
|
|
1187
|
+
);
|
|
1188
|
+
if (uncovered.length === 0) {
|
|
1189
|
+
return [finding4(at, "OK", `"${script}" covers every source directory under this workspace`)];
|
|
1190
|
+
}
|
|
1191
|
+
return [
|
|
1192
|
+
finding4(
|
|
1193
|
+
at,
|
|
1194
|
+
"WARN",
|
|
1195
|
+
`the "lint" script covers ${covered.join(", ")} and the pre-commit hook lints whatever is staged, so ${uncovered.map((name) => `${name}/**`).join(", ")} is linted by the hook alone \u2014 the package gate has never read those files, and the drift stays invisible until one moves across it. Widen the script, or say in it which globs are deliberately outside`
|
|
1196
|
+
)
|
|
1197
|
+
];
|
|
1198
|
+
});
|
|
1199
|
+
};
|
|
1200
|
+
var OBSERVABILITY = "@geonosis/observability";
|
|
1201
|
+
var COMPOSITION_ROOT = "src/platform.ts";
|
|
1202
|
+
var FLOOR_PACKAGES = [
|
|
1203
|
+
"@geonosis/conformance",
|
|
1204
|
+
"@geonosis/db",
|
|
1205
|
+
"@geonosis/events",
|
|
1206
|
+
"@geonosis/search",
|
|
1207
|
+
"@geonosis/workflows"
|
|
1208
|
+
];
|
|
1209
|
+
var COMPOSABLE = [
|
|
1210
|
+
{
|
|
1211
|
+
name: OBSERVABILITY,
|
|
1212
|
+
uncomposed: `a sink nobody composes at the composition root sends production errors nowhere, and from outside the process that reads exactly like a project with nothing to report. Build one where the app is wired (the recipe is in ${OBSERVABILITY}'s README, \xA74) and hand it the port`
|
|
1213
|
+
},
|
|
1214
|
+
...FLOOR_PACKAGES.map((name) => ({
|
|
1215
|
+
name,
|
|
1216
|
+
uncomposed: `a battery nothing composes is behaviour no request path can reach, and an install that does nothing reads from outside exactly like one that works. Compose it in the app\u2019s ${COMPOSITION_ROOT} \u2014 the composition root \`create-geonosis --domains\` writes \u2014 where the tenant session is opened before any store is built`
|
|
1217
|
+
}))
|
|
1218
|
+
];
|
|
1219
|
+
var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
|
|
1220
|
+
var composedHere = (root, workspaces) => {
|
|
1221
|
+
const declared = declaredAnywhere(workspaces);
|
|
1222
|
+
const wanted = COMPOSABLE.filter((one) => declared.has(one.name));
|
|
1223
|
+
if (wanted.length === 0) return [];
|
|
1224
|
+
const importing = /* @__PURE__ */ new Map();
|
|
1225
|
+
for (const path of filesUnder(root, (name) => SOURCE_FILE.test(name))) {
|
|
1226
|
+
let body;
|
|
1227
|
+
try {
|
|
1228
|
+
body = readFileSync7(path, "utf8");
|
|
1229
|
+
} catch {
|
|
1230
|
+
continue;
|
|
1231
|
+
}
|
|
1232
|
+
for (const one of wanted) {
|
|
1233
|
+
if (!importing.has(one.name) && body.includes(one.name)) importing.set(one.name, path);
|
|
1234
|
+
}
|
|
1235
|
+
if (importing.size === wanted.length) break;
|
|
1236
|
+
}
|
|
1237
|
+
return wanted.map((one) => {
|
|
1238
|
+
const at = importing.get(one.name);
|
|
1239
|
+
return at === void 0 ? finding4(
|
|
1240
|
+
one.name,
|
|
1241
|
+
"WARN",
|
|
1242
|
+
`it is declared here and no source file imports it \u2014 ${one.uncomposed}`
|
|
1243
|
+
) : finding4(
|
|
1244
|
+
one.name,
|
|
1245
|
+
"OK",
|
|
1246
|
+
`composed in ${relativePath(root, at)} \u2014 something here builds what it reports through`
|
|
1247
|
+
);
|
|
1248
|
+
});
|
|
1249
|
+
};
|
|
793
1250
|
var READERS = {
|
|
794
1251
|
ledger: "@geonosis/ledger",
|
|
795
1252
|
review: "@geonosis/review",
|
|
@@ -797,6 +1254,18 @@ var READERS = {
|
|
|
797
1254
|
verify: "@geonosis/verify",
|
|
798
1255
|
walk: "@geonosis/walk"
|
|
799
1256
|
};
|
|
1257
|
+
var doorBringing = (root, name, declared) => DOORS.filter((door) => declared.has(door)).find(
|
|
1258
|
+
(door) => Object.hasOwn(dependenciesOf(root, door), name)
|
|
1259
|
+
);
|
|
1260
|
+
var dependenciesOf = (root, name) => {
|
|
1261
|
+
try {
|
|
1262
|
+
return JSON.parse(
|
|
1263
|
+
readFileSync7(join7(packageDirOf(resolveFrom(root, name), name), "package.json"), "utf8")
|
|
1264
|
+
).dependencies ?? {};
|
|
1265
|
+
} catch {
|
|
1266
|
+
return {};
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
800
1269
|
var resolves = (root, name) => {
|
|
801
1270
|
try {
|
|
802
1271
|
resolveFrom(root, name);
|
|
@@ -818,7 +1287,7 @@ var declaredAnywhere = (workspaces) => new Set(
|
|
|
818
1287
|
var blocks = (root, config, readers, workspaces) => {
|
|
819
1288
|
if (config === void 0) {
|
|
820
1289
|
return [
|
|
821
|
-
|
|
1290
|
+
finding4(
|
|
822
1291
|
GEONOSIS,
|
|
823
1292
|
"SKIP",
|
|
824
1293
|
"there is no geonosis.json here, so no block says what this repo asks the kit to do"
|
|
@@ -828,11 +1297,21 @@ var blocks = (root, config, readers, workspaces) => {
|
|
|
828
1297
|
const declaredPackages = declaredAnywhere(workspaces);
|
|
829
1298
|
return Object.entries(readers).flatMap(([block, name]) => {
|
|
830
1299
|
const declared = config[block] !== void 0;
|
|
1300
|
+
const door = doorBringing(root, name, declaredPackages);
|
|
831
1301
|
const chosen = declaredPackages.has(name);
|
|
832
1302
|
const installed = resolves(root, name);
|
|
1303
|
+
if (declared && !chosen && door !== void 0) {
|
|
1304
|
+
return [
|
|
1305
|
+
finding4(
|
|
1306
|
+
name,
|
|
1307
|
+
"OK",
|
|
1308
|
+
`a "${block}" block, and ${door} brings ${name} to read it \u2014 #177: the root declares the door, not what the door owns`
|
|
1309
|
+
)
|
|
1310
|
+
];
|
|
1311
|
+
}
|
|
833
1312
|
if (declared && !chosen) {
|
|
834
1313
|
return [
|
|
835
|
-
|
|
1314
|
+
finding4(
|
|
836
1315
|
name,
|
|
837
1316
|
"WARN",
|
|
838
1317
|
`geonosis.json has a "${block}" block and no manifest here declares ${name} \u2014 nothing reads it${installed ? ", and the copy still under node_modules is what a stale install left behind" : ""}`
|
|
@@ -841,7 +1320,7 @@ var blocks = (root, config, readers, workspaces) => {
|
|
|
841
1320
|
}
|
|
842
1321
|
if (declared && !installed) {
|
|
843
1322
|
return [
|
|
844
|
-
|
|
1323
|
+
finding4(
|
|
845
1324
|
name,
|
|
846
1325
|
"WARN",
|
|
847
1326
|
`geonosis.json has a "${block}" block, a manifest declares ${name}, and it does not resolve here \u2014 run the install`
|
|
@@ -850,7 +1329,7 @@ var blocks = (root, config, readers, workspaces) => {
|
|
|
850
1329
|
}
|
|
851
1330
|
if (!declared && chosen && installed) {
|
|
852
1331
|
return [
|
|
853
|
-
|
|
1332
|
+
finding4(
|
|
854
1333
|
name,
|
|
855
1334
|
"WARN",
|
|
856
1335
|
`${name} is installed and geonosis.json has no "${block}" block \u2014 it runs on its defaults, whatever they are`
|
|
@@ -859,14 +1338,14 @@ var blocks = (root, config, readers, workspaces) => {
|
|
|
859
1338
|
}
|
|
860
1339
|
if (!declared && installed) {
|
|
861
1340
|
return [
|
|
862
|
-
|
|
1341
|
+
finding4(
|
|
863
1342
|
name,
|
|
864
1343
|
"OK",
|
|
865
1344
|
`${name} is here but no manifest declares it \u2014 a transitive dependency this repo did not choose, so it wants no "${block}" block`
|
|
866
1345
|
)
|
|
867
1346
|
];
|
|
868
1347
|
}
|
|
869
|
-
return declared ? [
|
|
1348
|
+
return declared ? [finding4(name, "OK", `a "${block}" block, and ${name} to read it`)] : [];
|
|
870
1349
|
});
|
|
871
1350
|
};
|
|
872
1351
|
var PLUGIN_DIR_RULE = "biological-architecture/no-unregistered-plugin-dir";
|
|
@@ -882,8 +1361,8 @@ var pluginDirsOf = (level) => {
|
|
|
882
1361
|
return { manifests: options.manifests ?? ["index.ts"], registry, roots: options.roots };
|
|
883
1362
|
};
|
|
884
1363
|
var pluginDirLayers = (root) => {
|
|
885
|
-
const path =
|
|
886
|
-
if (!
|
|
1364
|
+
const path = join7(root, ".oxlintrc.json");
|
|
1365
|
+
if (!existsSync6(path)) return [];
|
|
887
1366
|
const layers = [];
|
|
888
1367
|
for (const layer of layersOf(readConfig(path, root), PLUGIN_DIR_RULE)) {
|
|
889
1368
|
const dirs = pluginDirsOf(layer.level);
|
|
@@ -892,8 +1371,8 @@ var pluginDirLayers = (root) => {
|
|
|
892
1371
|
return layers;
|
|
893
1372
|
};
|
|
894
1373
|
var layerOver = (layers, root, relative) => {
|
|
895
|
-
const at =
|
|
896
|
-
const inside =
|
|
1374
|
+
const at = join7(root, relative);
|
|
1375
|
+
const inside = readdirSync3(at, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => `${relative}/${entry.name}`);
|
|
897
1376
|
const paths = inside.length === 0 ? [relative] : inside;
|
|
898
1377
|
const wrapped = layers.map((layer) => ({ files: layer.files, level: layer.dirs }));
|
|
899
1378
|
for (const path of paths) {
|
|
@@ -906,7 +1385,7 @@ var pluginDirs = (root) => {
|
|
|
906
1385
|
const layers = pluginDirLayers(root);
|
|
907
1386
|
if (layers.length === 0) {
|
|
908
1387
|
return [
|
|
909
|
-
|
|
1388
|
+
finding4(
|
|
910
1389
|
"plugin directories",
|
|
911
1390
|
"SKIP",
|
|
912
1391
|
"no-unregistered-plugin-dir is not configured here, so this repo has not said where its integrations live"
|
|
@@ -914,34 +1393,34 @@ var pluginDirs = (root) => {
|
|
|
914
1393
|
];
|
|
915
1394
|
}
|
|
916
1395
|
const missing = [...new Set(layers.flatMap((layer) => layer.dirs.registry))].filter(
|
|
917
|
-
(registry) => !
|
|
1396
|
+
(registry) => !existsSync6(join7(root, registry))
|
|
918
1397
|
);
|
|
919
1398
|
if (missing.length > 0) {
|
|
920
1399
|
return missing.map(
|
|
921
|
-
(registry) =>
|
|
1400
|
+
(registry) => finding4(registry, "FAIL", "the registry the rule names is not there")
|
|
922
1401
|
);
|
|
923
1402
|
}
|
|
924
1403
|
const source = /* @__PURE__ */ new Map();
|
|
925
1404
|
const unreachable = /* @__PURE__ */ new Map();
|
|
926
1405
|
for (const rootDir of new Set(layers.flatMap((layer) => layer.dirs.roots))) {
|
|
927
|
-
const at =
|
|
928
|
-
if (!
|
|
929
|
-
for (const entry of
|
|
1406
|
+
const at = join7(root, rootDir);
|
|
1407
|
+
if (!existsSync6(at)) continue;
|
|
1408
|
+
for (const entry of readdirSync3(at, { withFileTypes: true })) {
|
|
930
1409
|
if (!entry.isDirectory()) continue;
|
|
931
1410
|
const relative = `${rootDir}/${entry.name}`;
|
|
932
1411
|
const governs = layerOver(layers, root, relative);
|
|
933
1412
|
if (governs === void 0) continue;
|
|
934
1413
|
const key = governs.registry.join(", ");
|
|
935
|
-
const registry = source.get(key) ?? governs.registry.map((half) =>
|
|
1414
|
+
const registry = source.get(key) ?? governs.registry.map((half) => readFileSync7(join7(root, half), "utf8")).join("\n");
|
|
936
1415
|
source.set(key, registry);
|
|
937
|
-
const hasManifest = governs.manifests.some((name) =>
|
|
1416
|
+
const hasManifest = governs.manifests.some((name) => existsSync6(join7(at, entry.name, name)));
|
|
938
1417
|
if (!hasManifest || registry.includes(entry.name)) continue;
|
|
939
1418
|
unreachable.set(key, [...unreachable.get(key) ?? [], relative]);
|
|
940
1419
|
}
|
|
941
1420
|
}
|
|
942
1421
|
return [...source.keys()].toSorted().map((registry) => {
|
|
943
1422
|
const named2 = unreachable.get(registry) ?? [];
|
|
944
|
-
return named2.length === 0 ?
|
|
1423
|
+
return named2.length === 0 ? finding4(registry, "OK", "every directory under the declared roots is named by it") : finding4(
|
|
945
1424
|
registry,
|
|
946
1425
|
"FAIL",
|
|
947
1426
|
`${named2.length} directory(ies) it never names: ${named2.join(", ")}`
|
|
@@ -952,9 +1431,9 @@ var WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
|
952
1431
|
var HOIST_KEY = "publicHoistPattern";
|
|
953
1432
|
var HOIST_REPAIR = "rm -rf node_modules/.modules.yaml node_modules/.pnpm-workspace-state-v1.json && pnpm install";
|
|
954
1433
|
var hoistPatterns = (root) => {
|
|
955
|
-
const path =
|
|
956
|
-
if (!
|
|
957
|
-
const lines =
|
|
1434
|
+
const path = join7(root, WORKSPACE_YAML);
|
|
1435
|
+
if (!existsSync6(path)) return void 0;
|
|
1436
|
+
const lines = readFileSync7(path, "utf8").split("\n");
|
|
958
1437
|
const at = lines.findIndex((line) => new RegExp(`^${HOIST_KEY}\\s*:`).test(line));
|
|
959
1438
|
if (at < 0) return void 0;
|
|
960
1439
|
const patterns = [];
|
|
@@ -971,7 +1450,7 @@ var publicHoists = (root, workspaces) => {
|
|
|
971
1450
|
const patterns = hoistPatterns(root);
|
|
972
1451
|
if (patterns === void 0) {
|
|
973
1452
|
return [
|
|
974
|
-
|
|
1453
|
+
finding4(
|
|
975
1454
|
WORKSPACE_YAML,
|
|
976
1455
|
"SKIP",
|
|
977
1456
|
`no ${HOIST_KEY} is declared here, so nothing claims a package should be linked at the root`
|
|
@@ -985,92 +1464,31 @@ var publicHoists = (root, workspaces) => {
|
|
|
985
1464
|
const shape = matching(pattern);
|
|
986
1465
|
const hoisted = named2.filter((name) => shape.test(name));
|
|
987
1466
|
if (hoisted.length === 0) {
|
|
988
|
-
return
|
|
1467
|
+
return finding4(
|
|
989
1468
|
pattern,
|
|
990
1469
|
"SKIP",
|
|
991
1470
|
`this ${HOIST_KEY} matches no workspace package here, so what it should have linked at the root is a question this cannot answer`
|
|
992
1471
|
);
|
|
993
1472
|
}
|
|
994
|
-
const pruned = hoisted.filter((name) => !
|
|
1473
|
+
const pruned = hoisted.filter((name) => !existsSync6(join7(root, "node_modules", name)));
|
|
995
1474
|
if (pruned.length === 0) {
|
|
996
|
-
return
|
|
1475
|
+
return finding4(
|
|
997
1476
|
pattern,
|
|
998
1477
|
"OK",
|
|
999
1478
|
`every workspace package this ${HOIST_KEY} matches is linked at the root`
|
|
1000
1479
|
);
|
|
1001
1480
|
}
|
|
1002
|
-
return
|
|
1481
|
+
return finding4(
|
|
1003
1482
|
pattern,
|
|
1004
1483
|
"FAIL",
|
|
1005
1484
|
`the ${HOIST_KEY} \`${pattern}\` has no root link for ${pruned.map((name) => `node_modules/${name}`).join(", ")} \u2014 pnpm prunes one during an unrelated add and then answers "Already up to date" over a clean git status forever. Repair: ${HOIST_REPAIR}`
|
|
1006
1485
|
);
|
|
1007
1486
|
});
|
|
1008
1487
|
};
|
|
1009
|
-
var HOOK_FILES = ["lefthook.yml", "lefthook.yaml", ".lefthook.yml", ".lefthook.yaml"];
|
|
1010
|
-
var HOOK_DIRS = [".husky", ".githooks"];
|
|
1011
|
-
var VERSION_MANAGED = /(?:^|[\s;&|(])(?<runner>pnpm|npx|bunx|yarn)\b(?:\s+(?:exec|run|dlx|x))?\s+(?<bin>geonosis(?:-[a-z-]+)?)\b/;
|
|
1012
|
-
var NAMES_A_BIN = /(?:^|[\s;&|(/])geonosis(?:-[a-z-]+)?\b/;
|
|
1013
|
-
var hookLines = (root) => {
|
|
1014
|
-
const found = [];
|
|
1015
|
-
const read = (path) => {
|
|
1016
|
-
const at = relativePath(root, path);
|
|
1017
|
-
for (const line of readFileSync4(path, "utf8").split("\n")) {
|
|
1018
|
-
if (line.trim() !== "") found.push({ at, line });
|
|
1019
|
-
}
|
|
1020
|
-
};
|
|
1021
|
-
for (const name of HOOK_FILES) {
|
|
1022
|
-
const path = join4(root, name);
|
|
1023
|
-
if (existsSync3(path)) read(path);
|
|
1024
|
-
}
|
|
1025
|
-
for (const name of HOOK_DIRS) {
|
|
1026
|
-
const dir = join4(root, name);
|
|
1027
|
-
if (!existsSync3(dir)) continue;
|
|
1028
|
-
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
1029
|
-
if (entry.isFile() && !entry.name.startsWith("_") && !entry.name.startsWith(".")) {
|
|
1030
|
-
read(join4(dir, entry.name));
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
return found;
|
|
1035
|
-
};
|
|
1036
|
-
var gitHooks = (root) => {
|
|
1037
|
-
const lines = hookLines(root);
|
|
1038
|
-
if (lines.length === 0) {
|
|
1039
|
-
return [finding3("git hooks", "SKIP", "no lefthook, husky or .githooks file here to read")];
|
|
1040
|
-
}
|
|
1041
|
-
const files = [...new Set(lines.map((one) => one.at))].toSorted();
|
|
1042
|
-
return files.flatMap((at) => {
|
|
1043
|
-
const here = lines.filter((one) => one.at === at);
|
|
1044
|
-
const named2 = here.filter((one) => NAMES_A_BIN.test(one.line));
|
|
1045
|
-
if (named2.length === 0) {
|
|
1046
|
-
return [finding3(at, "SKIP", "it names no geonosis bin, so there is nothing here to start")];
|
|
1047
|
-
}
|
|
1048
|
-
const wrong = named2.flatMap((one) => {
|
|
1049
|
-
const found = VERSION_MANAGED.exec(one.line)?.groups;
|
|
1050
|
-
return found === void 0 ? [] : [{ bin: found["bin"] ?? "", runner: found["runner"] ?? "" }];
|
|
1051
|
-
});
|
|
1052
|
-
if (wrong.length === 0) {
|
|
1053
|
-
return [
|
|
1054
|
-
finding3(
|
|
1055
|
-
at,
|
|
1056
|
-
"OK",
|
|
1057
|
-
`${named2.length} geonosis bin(s) here, every one called directly rather than through a runner`
|
|
1058
|
-
)
|
|
1059
|
-
];
|
|
1060
|
-
}
|
|
1061
|
-
return wrong.map(
|
|
1062
|
-
({ bin, runner }) => finding3(
|
|
1063
|
-
at,
|
|
1064
|
-
"FAIL",
|
|
1065
|
-
`it runs ${bin} through ${runner} \u2014 a git hook's PATH is not the shell's and carries no version-manager shim, so ${runner} fails to START and what the author reads is its message, not this gate's. Call it directly: node_modules/.bin/${bin}`
|
|
1066
|
-
)
|
|
1067
|
-
);
|
|
1068
|
-
});
|
|
1069
|
-
};
|
|
1070
1488
|
var checkDrift = ({
|
|
1071
1489
|
readers = READERS,
|
|
1072
1490
|
root,
|
|
1073
|
-
userSettings =
|
|
1491
|
+
userSettings = join7(homedir(), SETTINGS),
|
|
1074
1492
|
workspaces
|
|
1075
1493
|
}) => {
|
|
1076
1494
|
const config = readGeonosis(root);
|
|
@@ -1083,103 +1501,28 @@ var checkDrift = ({
|
|
|
1083
1501
|
...generatedFiles(root, workspaces),
|
|
1084
1502
|
...pluginDirs(root),
|
|
1085
1503
|
...publicHoists(root, workspaces),
|
|
1086
|
-
...
|
|
1504
|
+
...composedHere(root, workspaces),
|
|
1505
|
+
...lintScopes(root, workspaces),
|
|
1506
|
+
...platformSplitBuilds(root),
|
|
1087
1507
|
...law(root, config),
|
|
1088
1508
|
...hooks(root, userSettings),
|
|
1089
1509
|
...blocks(root, config, readers, workspaces)
|
|
1090
1510
|
];
|
|
1091
1511
|
};
|
|
1092
1512
|
|
|
1093
|
-
// src/envelope.ts
|
|
1094
|
-
import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "fs";
|
|
1095
|
-
import { join as join5 } from "path";
|
|
1096
|
-
var ENVELOPES_DIR = ".geonosis/envelopes";
|
|
1097
|
-
var NO_ENVELOPES = "no .geonosis/envelopes/*.json \u2014 the tools write one per run, so an absent envelope is a run nobody has made here yet, and it is not a balanced one";
|
|
1098
|
-
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1099
|
-
var finding4 = (verdict, subject, message) => ({
|
|
1100
|
-
check: "envelope",
|
|
1101
|
-
message,
|
|
1102
|
-
subject,
|
|
1103
|
-
verdict
|
|
1104
|
-
});
|
|
1105
|
-
var lengthOf = (value) => Array.isArray(value) ? value.length : void 0;
|
|
1106
|
-
var NEXT = "Next: re-run the tool that wrote it and read the numbers it prints \u2014 a run that has lost count of its own inputs is a bug in that tool, not in this tree";
|
|
1107
|
-
var judge = (subject, name, parsed) => {
|
|
1108
|
-
if (!isRecord2(parsed)) return finding4("FAIL", subject, `is not an object. ${NEXT}`);
|
|
1109
|
-
const tool = parsed["tool"];
|
|
1110
|
-
if (typeof tool !== "string" || tool.trim() === "") {
|
|
1111
|
-
return finding4(
|
|
1112
|
-
"FAIL",
|
|
1113
|
-
subject,
|
|
1114
|
-
`names no tool, so nothing here says which run it is about. ${NEXT}`
|
|
1115
|
-
);
|
|
1116
|
-
}
|
|
1117
|
-
if (tool !== name) {
|
|
1118
|
-
return finding4(
|
|
1119
|
-
"FAIL",
|
|
1120
|
-
subject,
|
|
1121
|
-
`is written by "${tool}", not "${name}" \u2014 a tool writing another tool's envelope leaves both of their numbers unattributable. ${NEXT}`
|
|
1122
|
-
);
|
|
1123
|
-
}
|
|
1124
|
-
const version = parsed["version"];
|
|
1125
|
-
if (typeof version !== "string" || version.trim() === "") {
|
|
1126
|
-
return finding4(
|
|
1127
|
-
"FAIL",
|
|
1128
|
-
subject,
|
|
1129
|
-
`${tool}: names no version, so nothing dates this run and a stale envelope reads exactly like a fresh one. ${NEXT}`
|
|
1130
|
-
);
|
|
1131
|
-
}
|
|
1132
|
-
const considered = parsed["considered"];
|
|
1133
|
-
const read = parsed["read"];
|
|
1134
|
-
const refused = lengthOf(parsed["refused"]);
|
|
1135
|
-
const excused = lengthOf(parsed["excused"]);
|
|
1136
|
-
if (typeof considered !== "number" || typeof read !== "number" || refused === void 0 || excused === void 0) {
|
|
1137
|
-
return finding4(
|
|
1138
|
-
"FAIL",
|
|
1139
|
-
subject,
|
|
1140
|
-
`${tool}: has no considered/read/refused/excused to check \u2014 the four numbers ARE the envelope, and a file without them measures nothing. ${NEXT}`
|
|
1141
|
-
);
|
|
1142
|
-
}
|
|
1143
|
-
const accounted = read + refused + excused;
|
|
1144
|
-
return considered === accounted ? finding4(
|
|
1145
|
-
"OK",
|
|
1146
|
-
subject,
|
|
1147
|
-
`${tool} considered ${considered} and accounts for all of them \u2014 ${read} read + ${refused} refused + ${excused} excused`
|
|
1148
|
-
) : finding4(
|
|
1149
|
-
"FAIL",
|
|
1150
|
-
subject,
|
|
1151
|
-
`${tool}: considered ${considered} but accounts for ${accounted} \u2014 ${read} read + ${refused} refused + ${excused} excused. It reported on fewer things than it was handed, and every verdict it printed is over the smaller number. ${NEXT}`
|
|
1152
|
-
);
|
|
1153
|
-
};
|
|
1154
|
-
var checkEnvelopes = ({ root }) => {
|
|
1155
|
-
const dir = join5(root, ENVELOPES_DIR);
|
|
1156
|
-
const files = existsSync4(dir) ? readdirSync3(dir).filter((name) => name.endsWith(".json")).toSorted() : [];
|
|
1157
|
-
if (files.length === 0) return [finding4("SKIP", ENVELOPES_DIR, NO_ENVELOPES)];
|
|
1158
|
-
return files.map((name) => {
|
|
1159
|
-
const subject = `${ENVELOPES_DIR}/${name}`;
|
|
1160
|
-
let parsed;
|
|
1161
|
-
try {
|
|
1162
|
-
parsed = JSON.parse(readFileSync5(join5(dir, name), "utf8"));
|
|
1163
|
-
} catch (error) {
|
|
1164
|
-
return finding4("FAIL", subject, `is not readable JSON: ${error.message}. ${NEXT}`);
|
|
1165
|
-
}
|
|
1166
|
-
return judge(subject, name.replace(/\.json$/, ""), parsed);
|
|
1167
|
-
});
|
|
1168
|
-
};
|
|
1169
|
-
|
|
1170
1513
|
// src/exercised.ts
|
|
1171
1514
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1172
1515
|
import {
|
|
1173
1516
|
cpSync,
|
|
1174
|
-
existsSync as
|
|
1517
|
+
existsSync as existsSync7,
|
|
1175
1518
|
mkdirSync,
|
|
1176
1519
|
mkdtempSync,
|
|
1177
|
-
readFileSync as
|
|
1520
|
+
readFileSync as readFileSync8,
|
|
1178
1521
|
rmSync,
|
|
1179
1522
|
writeFileSync
|
|
1180
1523
|
} from "fs";
|
|
1181
1524
|
import { tmpdir } from "os";
|
|
1182
|
-
import { dirname as dirname2, join as
|
|
1525
|
+
import { dirname as dirname2, join as join8, resolve } from "path";
|
|
1183
1526
|
import { corpusOf, readManifest } from "@geonosis/lint-parity";
|
|
1184
1527
|
var OFF = /* @__PURE__ */ new Set([0, "0", "allow", "off", false]);
|
|
1185
1528
|
var severityOf = (level) => Array.isArray(level) ? level[0] : level;
|
|
@@ -1193,21 +1536,21 @@ var finding5 = (subject, verdict, message) => ({
|
|
|
1193
1536
|
});
|
|
1194
1537
|
var LIMIT = 800;
|
|
1195
1538
|
var refusal = (error) => {
|
|
1196
|
-
const
|
|
1197
|
-
return
|
|
1539
|
+
const said2 = String(error.message).trim();
|
|
1540
|
+
return said2.length > LIMIT ? `${said2.slice(0, LIMIT)}\u2026` : said2;
|
|
1198
1541
|
};
|
|
1199
1542
|
var reasonFrom = (config, oxlint) => {
|
|
1200
|
-
const dir = mkdtempSync(
|
|
1543
|
+
const dir = mkdtempSync(join8(tmpdir(), "geonosis-doctor-why-"));
|
|
1201
1544
|
try {
|
|
1202
|
-
const probe =
|
|
1545
|
+
const probe = join8(dir, "probe.tsx");
|
|
1203
1546
|
writeFileSync(probe, "export const probe = 1\n");
|
|
1204
1547
|
const run = spawnSync2(
|
|
1205
1548
|
oxlint,
|
|
1206
1549
|
["--no-ignore", "--disable-nested-config", "--config", config, probe],
|
|
1207
1550
|
{ encoding: "utf8", env: { ...process.env, NO_COLOR: "1" }, maxBuffer: 8 * 1024 * 1024 }
|
|
1208
1551
|
);
|
|
1209
|
-
const
|
|
1210
|
-
return /
|
|
1552
|
+
const said2 = `${run.stdout ?? ""}${run.stderr ?? ""}`;
|
|
1553
|
+
return /[A-Z]\w*:\s*([^]+?)(?=\s+at\s+\S+\s+\(|\n|$)/.exec(said2)?.[1];
|
|
1211
1554
|
} catch {
|
|
1212
1555
|
return void 0;
|
|
1213
1556
|
} finally {
|
|
@@ -1289,8 +1632,8 @@ var throughProbes = ({
|
|
|
1289
1632
|
}) => {
|
|
1290
1633
|
const refused = /* @__PURE__ */ new Map();
|
|
1291
1634
|
const placed = /* @__PURE__ */ new Set();
|
|
1292
|
-
const dir = mkdtempSync(
|
|
1293
|
-
const here =
|
|
1635
|
+
const dir = mkdtempSync(join8(tmpdir(), "geonosis-doctor-probe-"));
|
|
1636
|
+
const here = join8(dir, "corpus");
|
|
1294
1637
|
try {
|
|
1295
1638
|
cpSync(corpus, here, { recursive: true });
|
|
1296
1639
|
for (const rule of silent) {
|
|
@@ -1327,7 +1670,7 @@ var throughProbes = ({
|
|
|
1327
1670
|
mounted = rehomed;
|
|
1328
1671
|
}
|
|
1329
1672
|
for (const file of mounted) {
|
|
1330
|
-
const at2 =
|
|
1673
|
+
const at2 = join8(here, file.path);
|
|
1331
1674
|
if (placed.has(at2)) continue;
|
|
1332
1675
|
placed.add(at2);
|
|
1333
1676
|
mkdirSync(dirname2(at2), { recursive: true });
|
|
@@ -1340,12 +1683,12 @@ var throughProbes = ({
|
|
|
1340
1683
|
refused.set(rule, String(error.message));
|
|
1341
1684
|
}
|
|
1342
1685
|
}
|
|
1343
|
-
const probeConfig = JSON.parse(
|
|
1686
|
+
const probeConfig = JSON.parse(readFileSync8(config.path, "utf8"));
|
|
1344
1687
|
const configDir = dirname2(config.path);
|
|
1345
1688
|
probeConfig.jsPlugins = (probeConfig.jsPlugins ?? []).map(
|
|
1346
1689
|
(spec) => spec.startsWith(".") || spec.startsWith("/") ? resolve(configDir, spec) : packageDirOf(resolveFrom(configDir, spec), spec)
|
|
1347
1690
|
);
|
|
1348
|
-
const at =
|
|
1691
|
+
const at = join8(here, ".oxlintrc-geonosis-probe.json");
|
|
1349
1692
|
writeFileSync(at, JSON.stringify(probeConfig, null, 2));
|
|
1350
1693
|
const run = spawnSync2(
|
|
1351
1694
|
oxlint,
|
|
@@ -1366,6 +1709,7 @@ var throughProbes = ({
|
|
|
1366
1709
|
rmSync(dir, { force: true, recursive: true });
|
|
1367
1710
|
}
|
|
1368
1711
|
};
|
|
1712
|
+
var BASE = "fired in the corpus and the probes, which says nothing about this tree \u2014 a rule can be exercised there and reach nothing a workspace here actually writes";
|
|
1369
1713
|
var checkExercised = async ({
|
|
1370
1714
|
config,
|
|
1371
1715
|
corpus,
|
|
@@ -1374,15 +1718,15 @@ var checkExercised = async ({
|
|
|
1374
1718
|
repoCorpus,
|
|
1375
1719
|
root
|
|
1376
1720
|
}) => {
|
|
1377
|
-
const
|
|
1378
|
-
if (repoCorpus !== void 0 && !
|
|
1379
|
-
return
|
|
1721
|
+
const said2 = (verdict, message) => finding5(config.relative, verdict, message);
|
|
1722
|
+
if (repoCorpus !== void 0 && !existsSync7(repoCorpus)) {
|
|
1723
|
+
return said2(
|
|
1380
1724
|
"FAIL",
|
|
1381
1725
|
`geonosis.json declares a reach corpus at ${relativeToRoot(root, repoCorpus)} and there is nothing there \u2014 a corpus that cannot be read is a claim, not evidence`
|
|
1382
1726
|
);
|
|
1383
1727
|
}
|
|
1384
|
-
if (!
|
|
1385
|
-
return
|
|
1728
|
+
if (!existsSync7(corpus)) {
|
|
1729
|
+
return said2(
|
|
1386
1730
|
"SKIP",
|
|
1387
1731
|
`the plugin loaded from here ships no corpus at ${relativeToRoot(root, corpus)} \u2014 nothing declares which rules it can be evidence about`
|
|
1388
1732
|
);
|
|
@@ -1391,15 +1735,15 @@ var checkExercised = async ({
|
|
|
1391
1735
|
try {
|
|
1392
1736
|
manifest = readManifest(corpus);
|
|
1393
1737
|
} catch (error) {
|
|
1394
|
-
return
|
|
1738
|
+
return said2("SKIP", refusal(error));
|
|
1395
1739
|
}
|
|
1396
1740
|
const enabled = enabledHere(config, manifest.plugin);
|
|
1397
|
-
if (enabled.length === 0) return
|
|
1741
|
+
if (enabled.length === 0) return said2("SKIP", `no ${manifest.plugin} rule is enabled here`);
|
|
1398
1742
|
let reach;
|
|
1399
1743
|
try {
|
|
1400
1744
|
reach = corpusOf({ configA: config.path, configB: config.path, corpus, oxlint }).reach;
|
|
1401
1745
|
} catch (error) {
|
|
1402
|
-
return
|
|
1746
|
+
return said2(
|
|
1403
1747
|
"FAIL",
|
|
1404
1748
|
`oxlint refused to run this config over the corpus, so nothing here fired at all \u2014 ${reasonFrom(config.path, oxlint) ?? refusal(error)}`
|
|
1405
1749
|
);
|
|
@@ -1407,7 +1751,7 @@ var checkExercised = async ({
|
|
|
1407
1751
|
const fired = new Set(reach.filter((one) => one.firedInA).map((one) => one.rule));
|
|
1408
1752
|
const unknown = enabled.filter((rule) => !manifest.rules.includes(rule));
|
|
1409
1753
|
if (unknown.length > 0) {
|
|
1410
|
-
return
|
|
1754
|
+
return said2(
|
|
1411
1755
|
"FAIL",
|
|
1412
1756
|
`${countOf(unknown, "name")} the loaded plugin does not export: ${named(unknown)}`
|
|
1413
1757
|
);
|
|
@@ -1417,12 +1761,12 @@ var checkExercised = async ({
|
|
|
1417
1761
|
try {
|
|
1418
1762
|
own = ownReach({ config, oxlint, repoCorpus });
|
|
1419
1763
|
} catch (error) {
|
|
1420
|
-
return
|
|
1764
|
+
return said2("FAIL", `this repo's own corpus could not be run \u2014 ${refusal(error)}`);
|
|
1421
1765
|
}
|
|
1422
1766
|
}
|
|
1423
1767
|
const empty = own.claimed.filter((rule) => !own.fired.has(rule));
|
|
1424
1768
|
if (empty.length > 0) {
|
|
1425
|
-
return
|
|
1769
|
+
return said2(
|
|
1426
1770
|
"FAIL",
|
|
1427
1771
|
`${countOf(empty, "fire")} nowhere in this repo's own corpus, which names them: ${named(empty)}`
|
|
1428
1772
|
);
|
|
@@ -1430,7 +1774,7 @@ var checkExercised = async ({
|
|
|
1430
1774
|
const silent = enabled.filter((rule) => !fired.has(rule) && !own.fired.has(rule));
|
|
1431
1775
|
const where = own.claimed.length === 0 ? "" : ` (${own.fired.size} by this repo's own corpus)`;
|
|
1432
1776
|
if (silent.length === 0) {
|
|
1433
|
-
return
|
|
1777
|
+
return said2("OK", `${enabled.length} enabled, ${enabled.length} exercised${where} \u2014 ${BASE}`);
|
|
1434
1778
|
}
|
|
1435
1779
|
const loadedProbes = entry === void 0 ? null : await probesOf(entry, manifest.plugin).catch(() => null);
|
|
1436
1780
|
const probes = loadedProbes ?? {};
|
|
@@ -1441,37 +1785,121 @@ var checkExercised = async ({
|
|
|
1441
1785
|
try {
|
|
1442
1786
|
probed = throughProbes({ config, corpus, oxlint, probes, silent: declared });
|
|
1443
1787
|
} catch (error) {
|
|
1444
|
-
return
|
|
1788
|
+
return said2("FAIL", `the declared probes could not be run \u2014 ${refusal(error)}`);
|
|
1445
1789
|
}
|
|
1446
1790
|
}
|
|
1447
1791
|
const exercised = declared.filter((rule) => probed.fired.has(rule));
|
|
1448
1792
|
const inert = declared.filter((rule) => !probed.fired.has(rule));
|
|
1449
1793
|
const placed = inert.filter((rule) => !probed.refused.has(rule));
|
|
1450
1794
|
if (placed.length > 0) {
|
|
1451
|
-
return
|
|
1795
|
+
return said2(
|
|
1452
1796
|
"FAIL",
|
|
1453
1797
|
`${countOf(placed, "fire")} nowhere in the corpus and nothing through the probe each declares either: ${named(placed.map((rule) => under(config, rule)))}`
|
|
1454
1798
|
);
|
|
1455
1799
|
}
|
|
1456
1800
|
const unplaceable = inert.map((rule) => `${rule}: ${probed.refused.get(rule) ?? ""}`);
|
|
1457
|
-
const kitNote = unprobed.length === 0 || loadedProbes === null ? "" : `the loaded plugin ships no probe for ${unprobed.length === 1 ? "this enabled rule" : `${unprobed.length} enabled rules`} \u2014 the kit's omission, not this repo's (geonosis #131): ${unprobed.join(", ")}`;
|
|
1801
|
+
const kitNote = unprobed.length === 0 || loadedProbes === null ? "" : `the loaded plugin ships no probe for ${unprobed.length === 1 ? "this enabled rule" : `${unprobed.length} enabled rules`}, so the question cannot be asked here at all \u2014 the kit's omission, not this repo's, and nothing in this tree is the fix (geonosis #131): ${unprobed.join(", ")}`;
|
|
1458
1802
|
const unknowable = loadedProbes === null ? unprobed.map(
|
|
1459
1803
|
(rule) => `${rule} declares no probe, so ${optionSetsOf(config, rule).every((options) => options.length === 0) ? "its scope" : under(config, rule)} does not reach the corpus`
|
|
1460
1804
|
) : [];
|
|
1461
1805
|
if (unplaceable.length > 0 || unknowable.length > 0) {
|
|
1462
|
-
return
|
|
1806
|
+
return said2(
|
|
1463
1807
|
"UNJUDGED",
|
|
1464
1808
|
named([...unknowable, ...unplaceable]) + (kitNote === "" ? "" : ` \u2014 and ${kitNote}`)
|
|
1465
1809
|
);
|
|
1466
1810
|
}
|
|
1467
|
-
if (kitNote !== "") return
|
|
1811
|
+
if (kitNote !== "") return said2("SKIP", kitNote);
|
|
1468
1812
|
const through = exercised.length === 1 ? "1 through its declared probe under this repo\u2019s options" : `${exercised.length} through their declared probes under this repo\u2019s options`;
|
|
1469
|
-
return
|
|
1813
|
+
return said2(
|
|
1814
|
+
"OK",
|
|
1815
|
+
`${enabled.length} enabled, ${enabled.length} exercised${where} \u2014 ${through}. ${BASE}`
|
|
1816
|
+
);
|
|
1817
|
+
};
|
|
1818
|
+
|
|
1819
|
+
// src/envelope.ts
|
|
1820
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "fs";
|
|
1821
|
+
import { join as join9 } from "path";
|
|
1822
|
+
var ENVELOPES_DIR = ".geonosis/envelopes";
|
|
1823
|
+
var THIS_TOOL = "doctor";
|
|
1824
|
+
var OWN = `${THIS_TOOL}.json`;
|
|
1825
|
+
var NO_ENVELOPES = "no .geonosis/envelopes/*.json \u2014 the tools write one per run, so an absent envelope is a run nobody has made here yet, and it is not a balanced one (this run\u2019s own doctor.json is not one of them: it is written after these checks, and balanced at write time)";
|
|
1826
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1827
|
+
var finding6 = (verdict, subject, message) => ({
|
|
1828
|
+
check: "envelope",
|
|
1829
|
+
message,
|
|
1830
|
+
subject,
|
|
1831
|
+
verdict
|
|
1832
|
+
});
|
|
1833
|
+
var lengthOf = (value) => Array.isArray(value) ? value.length : void 0;
|
|
1834
|
+
var NEXT = "Next: re-run the tool that wrote it and read the numbers it prints \u2014 a run that has lost count of its own inputs is a bug in that tool, not in this tree";
|
|
1835
|
+
var judge = (subject, name, parsed) => {
|
|
1836
|
+
if (!isRecord2(parsed)) return finding6("FAIL", subject, `is not an object. ${NEXT}`);
|
|
1837
|
+
const tool = parsed["tool"];
|
|
1838
|
+
if (typeof tool !== "string" || tool.trim() === "") {
|
|
1839
|
+
return finding6(
|
|
1840
|
+
"FAIL",
|
|
1841
|
+
subject,
|
|
1842
|
+
`names no tool, so nothing here says which run it is about. ${NEXT}`
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1845
|
+
if (tool !== name) {
|
|
1846
|
+
return finding6(
|
|
1847
|
+
"FAIL",
|
|
1848
|
+
subject,
|
|
1849
|
+
`is written by "${tool}", not "${name}" \u2014 a tool writing another tool's envelope leaves both of their numbers unattributable. ${NEXT}`
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
const version = parsed["version"];
|
|
1853
|
+
if (typeof version !== "string" || version.trim() === "") {
|
|
1854
|
+
return finding6(
|
|
1855
|
+
"FAIL",
|
|
1856
|
+
subject,
|
|
1857
|
+
`${tool}: names no version, so nothing dates this run and a stale envelope reads exactly like a fresh one. ${NEXT}`
|
|
1858
|
+
);
|
|
1859
|
+
}
|
|
1860
|
+
const considered = parsed["considered"];
|
|
1861
|
+
const read = parsed["read"];
|
|
1862
|
+
const refused = lengthOf(parsed["refused"]);
|
|
1863
|
+
const excused = lengthOf(parsed["excused"]);
|
|
1864
|
+
if (typeof considered !== "number" || typeof read !== "number" || refused === void 0 || excused === void 0) {
|
|
1865
|
+
return finding6(
|
|
1866
|
+
"FAIL",
|
|
1867
|
+
subject,
|
|
1868
|
+
`${tool}: has no considered/read/refused/excused to check \u2014 the four numbers ARE the envelope, and a file without them measures nothing. ${NEXT}`
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
const accounted = read + refused + excused;
|
|
1872
|
+
return considered === accounted ? finding6(
|
|
1873
|
+
"OK",
|
|
1874
|
+
subject,
|
|
1875
|
+
`${tool} considered ${considered} and accounts for all of them \u2014 ${read} read + ${refused} refused + ${excused} excused`
|
|
1876
|
+
) : finding6(
|
|
1877
|
+
"FAIL",
|
|
1878
|
+
subject,
|
|
1879
|
+
`${tool}: considered ${considered} but accounts for ${accounted} \u2014 ${read} read + ${refused} refused + ${excused} excused. It reported on fewer things than it was handed, and every verdict it printed is over the smaller number. ${NEXT}`
|
|
1880
|
+
);
|
|
1881
|
+
};
|
|
1882
|
+
var checkEnvelopes = ({ root }) => {
|
|
1883
|
+
const dir = join9(root, ENVELOPES_DIR);
|
|
1884
|
+
const files = existsSync8(dir) ? readdirSync4(dir).filter((name) => name.endsWith(".json") && name !== OWN).toSorted() : [];
|
|
1885
|
+
if (files.length === 0) return [finding6("SKIP", ENVELOPES_DIR, NO_ENVELOPES)];
|
|
1886
|
+
return files.map((name) => {
|
|
1887
|
+
const subject = `${ENVELOPES_DIR}/${name}`;
|
|
1888
|
+
let parsed;
|
|
1889
|
+
try {
|
|
1890
|
+
parsed = JSON.parse(readFileSync9(join9(dir, name), "utf8"));
|
|
1891
|
+
} catch (error) {
|
|
1892
|
+
return finding6("FAIL", subject, `is not readable JSON: ${error.message}. ${NEXT}`);
|
|
1893
|
+
}
|
|
1894
|
+
return judge(subject, name.replace(/\.json$/, ""), parsed);
|
|
1895
|
+
});
|
|
1470
1896
|
};
|
|
1471
1897
|
|
|
1472
1898
|
// src/loaded.ts
|
|
1473
|
-
import {
|
|
1899
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
1900
|
+
import { join as join10, sep as sep3 } from "path";
|
|
1474
1901
|
var SCOPE = "@geonosis/";
|
|
1902
|
+
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.";
|
|
1475
1903
|
var BLOCKS = [
|
|
1476
1904
|
"dependencies",
|
|
1477
1905
|
"devDependencies",
|
|
@@ -1483,11 +1911,11 @@ var PINNED = /^[=v]?(\d+\.\d+\.\d+(?:[-+][\w.-]+)?)$/;
|
|
|
1483
1911
|
var RANGE = /^(\^|~|>=)\s*v?(\d+)\.(\d+)\.(\d+)$/;
|
|
1484
1912
|
var LINKED = /^(?:file|link|portal|workspace):/;
|
|
1485
1913
|
var ANY = /* @__PURE__ */ new Set(["", "*", "latest", "x"]);
|
|
1486
|
-
var
|
|
1914
|
+
var versionOf2 = (found, at) => Number(found[at] ?? Number.NaN);
|
|
1487
1915
|
var parts = (found) => [
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1916
|
+
versionOf2(found, 1),
|
|
1917
|
+
versionOf2(found, 2),
|
|
1918
|
+
versionOf2(found, 3)
|
|
1491
1919
|
];
|
|
1492
1920
|
var below = (here, bound) => {
|
|
1493
1921
|
for (const [at, one] of here.entries()) {
|
|
@@ -1508,9 +1936,9 @@ var satisfies = (version, spec) => {
|
|
|
1508
1936
|
if (here === null) return void 0;
|
|
1509
1937
|
const at = parts(here);
|
|
1510
1938
|
const bound = [
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1939
|
+
versionOf2(range, 2),
|
|
1940
|
+
versionOf2(range, 3),
|
|
1941
|
+
versionOf2(range, 4)
|
|
1514
1942
|
];
|
|
1515
1943
|
if (below(at, bound)) return false;
|
|
1516
1944
|
if (range[1] === ">=") return true;
|
|
@@ -1540,7 +1968,7 @@ var declaredFor = ({
|
|
|
1540
1968
|
}
|
|
1541
1969
|
return void 0;
|
|
1542
1970
|
};
|
|
1543
|
-
var
|
|
1971
|
+
var finding7 = (subject, verdict, message) => ({
|
|
1544
1972
|
check: "loaded",
|
|
1545
1973
|
message,
|
|
1546
1974
|
subject,
|
|
@@ -1557,36 +1985,36 @@ var oneConfig = async ({
|
|
|
1557
1985
|
specifier,
|
|
1558
1986
|
workspaces
|
|
1559
1987
|
}) => {
|
|
1560
|
-
const
|
|
1988
|
+
const said2 = (verdict, message) => finding7(config.relative, verdict, `${specifier}: ${message}`);
|
|
1561
1989
|
let loaded;
|
|
1562
1990
|
try {
|
|
1563
1991
|
loaded = await versionAt(resolveFrom(config.dir, specifier), specifier, root);
|
|
1564
1992
|
} catch (error) {
|
|
1565
|
-
return
|
|
1993
|
+
return said2(
|
|
1566
1994
|
"FAIL",
|
|
1567
1995
|
`could not resolve it from this config's directory, and oxlint resolves it from exactly there \u2014 ${firstLine(error)}`
|
|
1568
1996
|
);
|
|
1569
1997
|
}
|
|
1570
1998
|
const declared = declaredFor({ dir: config.dir, specifier, workspaces });
|
|
1571
1999
|
if (declared === void 0) {
|
|
1572
|
-
return
|
|
2000
|
+
return said2(
|
|
1573
2001
|
"FAIL",
|
|
1574
2002
|
`loaded ${loaded.version} from ${loaded.at}, and no package.json from here up to the root declares it \u2014 nothing says which version this was meant to be`
|
|
1575
2003
|
);
|
|
1576
2004
|
}
|
|
1577
2005
|
const held = satisfies(loaded.version, declared.spec);
|
|
1578
2006
|
if (held === void 0) {
|
|
1579
|
-
return
|
|
2007
|
+
return said2(
|
|
1580
2008
|
"FAIL",
|
|
1581
2009
|
`loaded ${loaded.version}, and ${declared.at} declares "${declared.spec}" \u2014 a range this check cannot read, so it will not call the version right`
|
|
1582
2010
|
);
|
|
1583
2011
|
}
|
|
1584
|
-
return held ?
|
|
2012
|
+
return held ? said2("OK", `loaded ${loaded.version} = declared ${declared.spec} (${declared.at})`) : said2(
|
|
1585
2013
|
"FAIL",
|
|
1586
|
-
`loaded ${loaded.version}, declared ${declared.spec} (${declared.at}) \u2014 a nested copy at ${loaded.at}.
|
|
2014
|
+
`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`
|
|
1587
2015
|
);
|
|
1588
2016
|
};
|
|
1589
|
-
var
|
|
2017
|
+
var labelOf2 = (workspace) => workspace.relative === "" ? "root" : workspace.relative;
|
|
1590
2018
|
var copiesOf = async ({
|
|
1591
2019
|
root,
|
|
1592
2020
|
specifier,
|
|
@@ -1603,31 +2031,81 @@ var copiesOf = async ({
|
|
|
1603
2031
|
const at = relativeToRoot(root, packageDirOf(entry, specifier));
|
|
1604
2032
|
const already = found.get(at);
|
|
1605
2033
|
if (already !== void 0) {
|
|
1606
|
-
already.from.push(
|
|
2034
|
+
already.from.push(labelOf2(workspace));
|
|
1607
2035
|
continue;
|
|
1608
2036
|
}
|
|
1609
|
-
found.set(at, { from: [
|
|
2037
|
+
found.set(at, { from: [labelOf2(workspace)], version: await pluginVersionOf(entry) });
|
|
1610
2038
|
}
|
|
1611
2039
|
if (found.size === 0) {
|
|
1612
|
-
return
|
|
2040
|
+
return finding7(specifier, "FAIL", "no workspace in this tree can resolve it at all");
|
|
1613
2041
|
}
|
|
1614
2042
|
const listed = [...found.entries()].map(([at, one]) => `${at} ${one.version} (${one.from.join(", ")})`).join("; ");
|
|
1615
|
-
return found.size === 1 ?
|
|
2043
|
+
return found.size === 1 ? finding7(specifier, "OK", `1 copy \u2014 ${listed}`) : finding7(
|
|
1616
2044
|
specifier,
|
|
1617
2045
|
"WARN",
|
|
1618
2046
|
`${found.size} copies \u2014 ${listed}. Which one oxlint runs depends on which directory its config sits in.`
|
|
1619
2047
|
);
|
|
1620
2048
|
};
|
|
2049
|
+
var shipperOf = (root, bin) => {
|
|
2050
|
+
const name = bin === "geonosis" ? "@geonosis/cli" : `@geonosis/${bin.replace(/^geonosis-/, "")}`;
|
|
2051
|
+
try {
|
|
2052
|
+
const manifest = JSON.parse(
|
|
2053
|
+
readFileSync10(join10(packageDirOf(resolveFrom(root, name), name), "package.json"), "utf8")
|
|
2054
|
+
);
|
|
2055
|
+
const declared = manifest.bin;
|
|
2056
|
+
return typeof declared === "object" && !Object.hasOwn(declared, bin) ? "" : name;
|
|
2057
|
+
} catch {
|
|
2058
|
+
return name;
|
|
2059
|
+
}
|
|
2060
|
+
};
|
|
2061
|
+
var knipNote = (root, bin) => {
|
|
2062
|
+
const shipper = shipperOf(root, bin);
|
|
2063
|
+
return shipper === "" ? "" : `. knip resolves no dependency out of that path, so add ignoreDependencies: ["${shipper}"] to its config in the same commit`;
|
|
2064
|
+
};
|
|
2065
|
+
var gitHooks = (root) => {
|
|
2066
|
+
const lines = hookLines(root);
|
|
2067
|
+
if (lines.length === 0) {
|
|
2068
|
+
return [finding7("git hooks", "SKIP", "no lefthook, husky or .githooks file here to read")];
|
|
2069
|
+
}
|
|
2070
|
+
const files = [...new Set(lines.map((one) => one.at))].toSorted();
|
|
2071
|
+
return files.flatMap((at) => {
|
|
2072
|
+
const here = lines.filter((one) => one.at === at);
|
|
2073
|
+
const named2 = here.filter((one) => NAMES_A_BIN.test(one.line));
|
|
2074
|
+
if (named2.length === 0) {
|
|
2075
|
+
return [finding7(at, "SKIP", "it names no geonosis bin, so there is nothing here to start")];
|
|
2076
|
+
}
|
|
2077
|
+
const wrong = named2.flatMap((one) => {
|
|
2078
|
+
const found = VERSION_MANAGED.exec(one.line)?.groups;
|
|
2079
|
+
return found === void 0 ? [] : [{ bin: found["bin"] ?? "", runner: found["runner"] ?? "" }];
|
|
2080
|
+
});
|
|
2081
|
+
if (wrong.length === 0) {
|
|
2082
|
+
return [
|
|
2083
|
+
finding7(
|
|
2084
|
+
at,
|
|
2085
|
+
"OK",
|
|
2086
|
+
`${named2.length} geonosis bin(s) here, every one called directly rather than through a runner`
|
|
2087
|
+
)
|
|
2088
|
+
];
|
|
2089
|
+
}
|
|
2090
|
+
return wrong.map(
|
|
2091
|
+
({ bin, runner }) => finding7(
|
|
2092
|
+
at,
|
|
2093
|
+
"FAIL",
|
|
2094
|
+
`it runs ${bin} through ${runner} \u2014 a git hook's PATH is not the shell's and carries no version-manager shim, so ${runner} fails to START and what the author reads is its message, not this gate's. Call it directly: node_modules/.bin/${bin}${knipNote(root, bin)}`
|
|
2095
|
+
)
|
|
2096
|
+
);
|
|
2097
|
+
});
|
|
2098
|
+
};
|
|
1621
2099
|
var checkLoaded = async ({
|
|
1622
2100
|
configs,
|
|
1623
2101
|
root,
|
|
1624
2102
|
workspaces
|
|
1625
2103
|
}) => {
|
|
1626
|
-
const findings = [];
|
|
2104
|
+
const findings = [...gitHooks(root)];
|
|
1627
2105
|
const specifiers = /* @__PURE__ */ new Set();
|
|
1628
2106
|
for (const config of configs) {
|
|
1629
2107
|
if (config.error !== void 0) {
|
|
1630
|
-
findings.push(
|
|
2108
|
+
findings.push(finding7(config.relative, "FAIL", config.error));
|
|
1631
2109
|
continue;
|
|
1632
2110
|
}
|
|
1633
2111
|
for (const specifier of config.jsPlugins.filter((name) => name.startsWith(SCOPE))) {
|
|
@@ -1642,13 +2120,13 @@ var checkLoaded = async ({
|
|
|
1642
2120
|
};
|
|
1643
2121
|
|
|
1644
2122
|
// src/observability.ts
|
|
1645
|
-
import { readFileSync as
|
|
1646
|
-
import { join as
|
|
1647
|
-
var
|
|
2123
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
2124
|
+
import { join as join11 } from "path";
|
|
2125
|
+
var GEONOSIS_FILE2 = "geonosis.json";
|
|
1648
2126
|
var REACHES_NOTHING = /* @__PURE__ */ new Set(["console", "memory", "noop", "none", "null", "swallowing"]);
|
|
1649
2127
|
var DEFAULT_MAX_AGE_SECONDS = 3600;
|
|
1650
2128
|
var HEAD_TIMEOUT_MS = 3e3;
|
|
1651
|
-
var
|
|
2129
|
+
var finding8 = (verdict, subject, message) => ({
|
|
1652
2130
|
check: "observability",
|
|
1653
2131
|
message,
|
|
1654
2132
|
subject,
|
|
@@ -1657,7 +2135,7 @@ var finding7 = (verdict, subject, message) => ({
|
|
|
1657
2135
|
var readGeonosis2 = (root) => {
|
|
1658
2136
|
let text;
|
|
1659
2137
|
try {
|
|
1660
|
-
text =
|
|
2138
|
+
text = readFileSync11(join11(root, GEONOSIS_FILE2), "utf8");
|
|
1661
2139
|
} catch {
|
|
1662
2140
|
return { present: false };
|
|
1663
2141
|
}
|
|
@@ -1675,27 +2153,27 @@ var readGeonosis2 = (root) => {
|
|
|
1675
2153
|
var exporterFinding = (config) => {
|
|
1676
2154
|
const sink = config.sink;
|
|
1677
2155
|
if (typeof sink !== "string" || sink.trim() === "") {
|
|
1678
|
-
return
|
|
2156
|
+
return finding8(
|
|
1679
2157
|
"FAIL",
|
|
1680
|
-
|
|
2158
|
+
GEONOSIS_FILE2,
|
|
1681
2159
|
"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"
|
|
1682
2160
|
);
|
|
1683
2161
|
}
|
|
1684
2162
|
if (REACHES_NOTHING.has(sink.toLowerCase())) {
|
|
1685
|
-
return
|
|
2163
|
+
return finding8(
|
|
1686
2164
|
"WARN",
|
|
1687
|
-
|
|
2165
|
+
GEONOSIS_FILE2,
|
|
1688
2166
|
`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.`
|
|
1689
2167
|
);
|
|
1690
2168
|
}
|
|
1691
|
-
return
|
|
2169
|
+
return finding8("OK", GEONOSIS_FILE2, `the configured sink is "${sink}"`);
|
|
1692
2170
|
};
|
|
1693
2171
|
var reachableFinding = async (config) => {
|
|
1694
2172
|
const endpoint = config.endpoint;
|
|
1695
2173
|
if (typeof endpoint !== "string" || endpoint.trim() === "") {
|
|
1696
|
-
return
|
|
2174
|
+
return finding8(
|
|
1697
2175
|
"SKIP",
|
|
1698
|
-
|
|
2176
|
+
GEONOSIS_FILE2,
|
|
1699
2177
|
"no observability.endpoint was named, so whether the exporter is reachable was not asked"
|
|
1700
2178
|
);
|
|
1701
2179
|
}
|
|
@@ -1703,15 +2181,15 @@ var reachableFinding = async (config) => {
|
|
|
1703
2181
|
const timer = setTimeout(() => controller.abort(), HEAD_TIMEOUT_MS);
|
|
1704
2182
|
try {
|
|
1705
2183
|
const response = await fetch(endpoint, { method: "HEAD", signal: controller.signal });
|
|
1706
|
-
return
|
|
2184
|
+
return finding8(
|
|
1707
2185
|
"OK",
|
|
1708
|
-
|
|
2186
|
+
GEONOSIS_FILE2,
|
|
1709
2187
|
`${endpoint} is reachable \u2014 it answered ${response.status} to a HEAD`
|
|
1710
2188
|
);
|
|
1711
2189
|
} catch (error) {
|
|
1712
|
-
return
|
|
2190
|
+
return finding8(
|
|
1713
2191
|
"FAIL",
|
|
1714
|
-
|
|
2192
|
+
GEONOSIS_FILE2,
|
|
1715
2193
|
`${endpoint} is not reachable from here: ${error.message}. Every report this repo sends is going into that.`
|
|
1716
2194
|
);
|
|
1717
2195
|
} finally {
|
|
@@ -1721,36 +2199,36 @@ var reachableFinding = async (config) => {
|
|
|
1721
2199
|
var ageFinding = (config, root, now) => {
|
|
1722
2200
|
const file = config.lastEventFile;
|
|
1723
2201
|
if (typeof file !== "string" || file.trim() === "") {
|
|
1724
|
-
return
|
|
2202
|
+
return finding8(
|
|
1725
2203
|
"SKIP",
|
|
1726
|
-
|
|
2204
|
+
GEONOSIS_FILE2,
|
|
1727
2205
|
"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."
|
|
1728
2206
|
);
|
|
1729
2207
|
}
|
|
1730
2208
|
const maxAgeSeconds = typeof config.maxAgeSeconds === "number" && config.maxAgeSeconds > 0 ? config.maxAgeSeconds : DEFAULT_MAX_AGE_SECONDS;
|
|
1731
2209
|
let record;
|
|
1732
2210
|
try {
|
|
1733
|
-
record = JSON.parse(
|
|
2211
|
+
record = JSON.parse(readFileSync11(join11(root, file), "utf8"));
|
|
1734
2212
|
} catch (error) {
|
|
1735
|
-
return
|
|
2213
|
+
return finding8(
|
|
1736
2214
|
"FAIL",
|
|
1737
2215
|
file,
|
|
1738
2216
|
`the last event file could not be read: ${error.message}. A sink that has never written one has never captured anything.`
|
|
1739
2217
|
);
|
|
1740
2218
|
}
|
|
1741
2219
|
if (typeof record.at !== "number" || !Number.isFinite(record.at)) {
|
|
1742
|
-
return
|
|
2220
|
+
return finding8(
|
|
1743
2221
|
"FAIL",
|
|
1744
2222
|
file,
|
|
1745
2223
|
'the last event record has no numeric "at", so its age cannot be read \u2014 and an age nobody can read is not an age inside the window'
|
|
1746
2224
|
);
|
|
1747
2225
|
}
|
|
1748
2226
|
const ageSeconds = Math.round((now - record.at) / 1e3);
|
|
1749
|
-
return ageSeconds > maxAgeSeconds ?
|
|
2227
|
+
return ageSeconds > maxAgeSeconds ? finding8(
|
|
1750
2228
|
"FAIL",
|
|
1751
2229
|
file,
|
|
1752
2230
|
`the last event arrived ${ageSeconds}s ago, past the ${maxAgeSeconds}s window. An exporter that stopped, a key that was rotated and a sink that has been dropping since Tuesday all look exactly like this, and all of them leave a green build.`
|
|
1753
|
-
) :
|
|
2231
|
+
) : finding8(
|
|
1754
2232
|
"OK",
|
|
1755
2233
|
file,
|
|
1756
2234
|
`the last event arrived ${ageSeconds}s ago, inside the ${maxAgeSeconds}s window`
|
|
@@ -1759,18 +2237,18 @@ var ageFinding = (config, root, now) => {
|
|
|
1759
2237
|
var probeFinding = (config) => {
|
|
1760
2238
|
const probe = config.probe;
|
|
1761
2239
|
if (typeof probe === "string" && probe.trim() !== "") {
|
|
1762
|
-
return
|
|
2240
|
+
return finding8("OK", GEONOSIS_FILE2, `the probe that proves this exporter is "${probe}"`);
|
|
1763
2241
|
}
|
|
1764
2242
|
if (typeof config.lastEventFile === "string" && config.lastEventFile.trim() !== "") {
|
|
1765
|
-
return
|
|
2243
|
+
return finding8(
|
|
1766
2244
|
"OK",
|
|
1767
|
-
|
|
2245
|
+
GEONOSIS_FILE2,
|
|
1768
2246
|
"no probe command, but a last event file is read above, so something does look at this exporter"
|
|
1769
2247
|
);
|
|
1770
2248
|
}
|
|
1771
|
-
return
|
|
2249
|
+
return finding8(
|
|
1772
2250
|
"WARN",
|
|
1773
|
-
|
|
2251
|
+
GEONOSIS_FILE2,
|
|
1774
2252
|
"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."
|
|
1775
2253
|
);
|
|
1776
2254
|
};
|
|
@@ -1781,19 +2259,19 @@ var checkObservability = async ({
|
|
|
1781
2259
|
const read = readGeonosis2(root);
|
|
1782
2260
|
if (read.error !== void 0) {
|
|
1783
2261
|
return [
|
|
1784
|
-
|
|
2262
|
+
finding8(
|
|
1785
2263
|
"FAIL",
|
|
1786
|
-
|
|
1787
|
-
`${
|
|
2264
|
+
GEONOSIS_FILE2,
|
|
2265
|
+
`${GEONOSIS_FILE2} 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.`
|
|
1788
2266
|
)
|
|
1789
2267
|
];
|
|
1790
2268
|
}
|
|
1791
2269
|
if (!read.present || read.config === void 0) {
|
|
1792
2270
|
return [
|
|
1793
|
-
|
|
2271
|
+
finding8(
|
|
1794
2272
|
"SKIP",
|
|
1795
|
-
|
|
1796
|
-
`no observability block in ${
|
|
2273
|
+
GEONOSIS_FILE2,
|
|
2274
|
+
`no observability block in ${GEONOSIS_FILE2}, so nothing here knows where this repo sends its errors. Add { sink, endpoint, lastEventFile | probe, maxAgeSeconds } to have this asked.`
|
|
1797
2275
|
)
|
|
1798
2276
|
];
|
|
1799
2277
|
}
|
|
@@ -1806,28 +2284,14 @@ var checkObservability = async ({
|
|
|
1806
2284
|
];
|
|
1807
2285
|
};
|
|
1808
2286
|
|
|
1809
|
-
// src/repo-corpus.ts
|
|
1810
|
-
import { existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
|
|
1811
|
-
import { join as join8 } from "path";
|
|
1812
|
-
var GEONOSIS_FILE2 = "geonosis.json";
|
|
1813
|
-
var repoCorpusOf = (root) => {
|
|
1814
|
-
const path = join8(root, GEONOSIS_FILE2);
|
|
1815
|
-
if (!existsSync6(path)) return void 0;
|
|
1816
|
-
try {
|
|
1817
|
-
const parsed = JSON.parse(readFileSync8(path, "utf8"));
|
|
1818
|
-
const declared = parsed.doctor?.corpus;
|
|
1819
|
-
return typeof declared === "string" && declared !== "" ? join8(root, declared) : void 0;
|
|
1820
|
-
} catch {
|
|
1821
|
-
return void 0;
|
|
1822
|
-
}
|
|
1823
|
-
};
|
|
1824
|
-
|
|
1825
2287
|
// src/runner.ts
|
|
2288
|
+
import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync12 } from "fs";
|
|
2289
|
+
import { join as join12 } from "path";
|
|
1826
2290
|
var TEST_FAILURES = "testFailures";
|
|
1827
2291
|
var RUNS_A_RUNNER = /(?:^|[\s;&|(])(?:npx\s+|bunx\s+|pnpm\s+(?:exec\s+)?)?(?:vitest|bun\s+test)(?:\s|$)/;
|
|
1828
2292
|
var RUNS_BUN_TEST = /(?:^|[\s;&|(])(?:bunx\s+)?bun\s+test(?:\s|$)/;
|
|
1829
2293
|
var WRITES_A_REPORT = /--reporter[= ]\S*json|--outputFile/i;
|
|
1830
|
-
var
|
|
2294
|
+
var finding9 = (subject, verdict, message) => ({
|
|
1831
2295
|
check: "runner",
|
|
1832
2296
|
message,
|
|
1833
2297
|
subject,
|
|
@@ -1845,57 +2309,355 @@ var reportingCounters = (ratchet) => (ratchet?.counters ?? []).filter(
|
|
|
1845
2309
|
(entry) => entry.counter === TEST_FAILURES && stringOf(entry.report) !== ""
|
|
1846
2310
|
);
|
|
1847
2311
|
var keyOf = (entry) => stringOf(entry.key) === "" ? TEST_FAILURES : stringOf(entry.key);
|
|
2312
|
+
var PROVES = /--prove\b/;
|
|
2313
|
+
var everythingRun2 = (root, workspaces) => {
|
|
2314
|
+
const scripts = workspaces.flatMap((one) => Object.values(one.manifest.scripts ?? {}));
|
|
2315
|
+
const dir = join12(root, ".github/workflows");
|
|
2316
|
+
const workflows = (existsSync9(dir) ? readdirSync5(dir) : []).filter((name) => name.endsWith(".yml") || name.endsWith(".yaml")).map((name) => {
|
|
2317
|
+
try {
|
|
2318
|
+
return readFileSync12(join12(dir, name), "utf8");
|
|
2319
|
+
} catch {
|
|
2320
|
+
return "";
|
|
2321
|
+
}
|
|
2322
|
+
});
|
|
2323
|
+
let tiers = {};
|
|
2324
|
+
try {
|
|
2325
|
+
const config = JSON.parse(readFileSync12(join12(root, "geonosis.json"), "utf8"));
|
|
2326
|
+
tiers = config.verify ?? {};
|
|
2327
|
+
} catch {
|
|
2328
|
+
}
|
|
2329
|
+
return {
|
|
2330
|
+
run: [...Object.values(tiers).flat(), ...scripts, ...workflows].join("\n"),
|
|
2331
|
+
tiers: Object.keys(tiers)
|
|
2332
|
+
};
|
|
2333
|
+
};
|
|
2334
|
+
var proveIsWired = ({
|
|
2335
|
+
ratchet,
|
|
2336
|
+
root,
|
|
2337
|
+
workspaces
|
|
2338
|
+
}) => {
|
|
2339
|
+
if (ratchet === void 0 || ratchet.counters.length === 0) return [];
|
|
2340
|
+
const { run, tiers } = everythingRun2(root, workspaces);
|
|
2341
|
+
if (PROVES.test(run)) {
|
|
2342
|
+
return [
|
|
2343
|
+
finding9(
|
|
2344
|
+
RATCHET_FILE,
|
|
2345
|
+
"OK",
|
|
2346
|
+
`${ratchet.counters.length} counter(s), and something here runs geonosis-ratchet --prove`
|
|
2347
|
+
)
|
|
2348
|
+
];
|
|
2349
|
+
}
|
|
2350
|
+
const where = tiers.at(-1) ?? "full";
|
|
2351
|
+
return [
|
|
2352
|
+
finding9(
|
|
2353
|
+
RATCHET_FILE,
|
|
2354
|
+
"WARN",
|
|
2355
|
+
`${ratchet.counters.length} counter(s) and nothing here ever runs geonosis-ratchet --prove \u2014 not a verify tier, not a script, not a workflow. A counter that has never been seen reading its own planted finding has not been shown to measure anything, and it reads exactly like a clean tree. Add \`geonosis-ratchet --prove\` to the "${where}" tier in geonosis.json, or to the workflow that runs it`
|
|
2356
|
+
)
|
|
2357
|
+
];
|
|
2358
|
+
};
|
|
1848
2359
|
var checkRunner = ({
|
|
1849
2360
|
ratchet,
|
|
2361
|
+
root,
|
|
1850
2362
|
workspaces
|
|
1851
2363
|
}) => {
|
|
1852
2364
|
const reading = reportingCounters(ratchet);
|
|
1853
|
-
|
|
2365
|
+
const perWorkspace2 = workspaces.flatMap((workspace) => {
|
|
1854
2366
|
const script = stringOf(workspace.manifest.scripts?.test);
|
|
1855
2367
|
if (script === "") return [];
|
|
1856
2368
|
const subject = workspace.relative === "" ? "package.json" : `${workspace.relative}/package.json`;
|
|
1857
|
-
const
|
|
1858
|
-
|
|
2369
|
+
const said2 = (verdict, message) => [
|
|
2370
|
+
finding9(subject, verdict, message)
|
|
1859
2371
|
];
|
|
1860
2372
|
if (!RUNS_A_RUNNER.test(script)) {
|
|
1861
|
-
return
|
|
2373
|
+
return said2(
|
|
1862
2374
|
"OK",
|
|
1863
2375
|
`"${script}" runs neither vitest nor bun test \u2014 this check has nothing to say about it`
|
|
1864
2376
|
);
|
|
1865
2377
|
}
|
|
1866
2378
|
if (testFilesUnder(workspace.dir).length === 0) {
|
|
1867
|
-
return
|
|
2379
|
+
return said2(
|
|
1868
2380
|
"SKIP",
|
|
1869
2381
|
`"${script}" runs a test runner and there is no test file under this workspace \u2014 nothing here for its exit code to be wrong about`
|
|
1870
2382
|
);
|
|
1871
2383
|
}
|
|
1872
2384
|
if (WRITES_A_REPORT.test(script)) {
|
|
1873
|
-
return
|
|
2385
|
+
return said2("OK", "the script asks the runner for its own JSON report, not for a status code");
|
|
1874
2386
|
}
|
|
1875
2387
|
const counter = reading.find((entry) => covers(stringOf(entry.command), workspace, workspaces));
|
|
1876
2388
|
if (counter !== void 0) {
|
|
1877
|
-
return
|
|
2389
|
+
return said2("OK", `read by the ratchet's "${keyOf(counter)}" counter in report mode`);
|
|
1878
2390
|
}
|
|
1879
2391
|
if (RUNS_BUN_TEST.test(script)) {
|
|
1880
|
-
return
|
|
2392
|
+
return said2(
|
|
1881
2393
|
"OK",
|
|
1882
2394
|
`"${script}" is judged by its exit code, and bun test's exit code is a verdict: a planted failure exits 1, measured on bun 1.4.0 (2026-08-30). Nothing further is required here.`
|
|
1883
2395
|
);
|
|
1884
2396
|
}
|
|
1885
|
-
return
|
|
2397
|
+
return said2(
|
|
1886
2398
|
"WARN",
|
|
1887
2399
|
`"${script}" runs vitest, whose exit code is the only verdict here; @cloudflare/vitest-pool-workers exited 0 on failing tests for weeks in a consumer. Give the ratchet a ${TEST_FAILURES} counter in report mode covering this workspace, or have the script write a JSON report.`
|
|
1888
2400
|
);
|
|
1889
2401
|
});
|
|
2402
|
+
return [...proveIsWired({ ratchet, root, workspaces }), ...perWorkspace2];
|
|
1890
2403
|
};
|
|
1891
2404
|
|
|
1892
2405
|
// src/doctor.ts
|
|
2406
|
+
import { homedir as homedir2 } from "os";
|
|
2407
|
+
import { join as join14 } from "path";
|
|
1893
2408
|
import { resolveOxlint } from "@geonosis/lint-parity";
|
|
2409
|
+
|
|
2410
|
+
// src/engine.ts
|
|
2411
|
+
import { sep as sep4 } from "path";
|
|
2412
|
+
var declaredIn3 = (manifest) => new Set(
|
|
2413
|
+
[
|
|
2414
|
+
manifest.dependencies,
|
|
2415
|
+
manifest.devDependencies,
|
|
2416
|
+
manifest.optionalDependencies,
|
|
2417
|
+
manifest.peerDependencies
|
|
2418
|
+
].flatMap((block) => Object.keys(block ?? {}))
|
|
2419
|
+
);
|
|
2420
|
+
var holds3 = (parent, child) => child === parent || child.startsWith(`${parent}${sep4}`);
|
|
2421
|
+
var labelOf3 = (workspace) => workspace.relative === "" ? "root" : workspace.relative;
|
|
2422
|
+
var governedBy = (dir, workspaces) => workspaces.filter((one) => holds3(one.dir, dir) || holds3(dir, one.dir));
|
|
2423
|
+
var enabledHere2 = (config, plugin) => [
|
|
2424
|
+
.../* @__PURE__ */ new Set([
|
|
2425
|
+
...enabledRulesOf(config.rules, plugin),
|
|
2426
|
+
...config.overrides.flatMap((one) => enabledRulesOf(one.rules, plugin))
|
|
2427
|
+
])
|
|
2428
|
+
];
|
|
2429
|
+
var checkEngines = async ({
|
|
2430
|
+
config,
|
|
2431
|
+
entry,
|
|
2432
|
+
workspaces
|
|
2433
|
+
}) => {
|
|
2434
|
+
const plugin = await presumptionsOf(entry).catch(() => void 0);
|
|
2435
|
+
if (plugin === void 0 || plugin.namespace === "" || Object.keys(plugin.presumed).length === 0) {
|
|
2436
|
+
return [];
|
|
2437
|
+
}
|
|
2438
|
+
const governed = governedBy(config.dir, workspaces);
|
|
2439
|
+
const declared = new Set(governed.flatMap((one) => [...declaredIn3(one.manifest)]));
|
|
2440
|
+
const asked = governed.map(labelOf3).join(", ");
|
|
2441
|
+
return enabledHere2(config, plugin.namespace).flatMap((rule) => {
|
|
2442
|
+
const presumes = plugin.presumed[rule.slice(plugin.namespace.length + 1)];
|
|
2443
|
+
if (presumes === void 0 || presumes.packages.some((name) => declared.has(name))) return [];
|
|
2444
|
+
return [
|
|
2445
|
+
{
|
|
2446
|
+
check: "exercised",
|
|
2447
|
+
message: `it presumes ${presumes.engine}, and none of the manifests this config governs (${asked}) declares ${presumes.packages.join(" or ")} \u2014 enabled here it has no possible finding, which reads exactly like a clean tree. Drop it from this config, or say which package brings the engine`,
|
|
2448
|
+
subject: `${config.relative} \u2192 ${rule}`,
|
|
2449
|
+
verdict: "WARN"
|
|
2450
|
+
}
|
|
2451
|
+
];
|
|
2452
|
+
});
|
|
2453
|
+
};
|
|
2454
|
+
|
|
2455
|
+
// src/rails.ts
|
|
2456
|
+
import { existsSync as existsSync10, readFileSync as readFileSync13 } from "fs";
|
|
2457
|
+
import { join as join13 } from "path";
|
|
2458
|
+
var GEONOSIS2 = "geonosis.json";
|
|
2459
|
+
var PROJECT = ".claude/settings.json";
|
|
2460
|
+
var LOCAL = ".claude/settings.local.json";
|
|
2461
|
+
var RUN_RECORD = ".geonosis/rails-run.json";
|
|
2462
|
+
var managedSettingsPath = (platform = process.platform) => platform === "darwin" ? "/Library/Application Support/ClaudeCode/managed-settings.json" : "/etc/claude-code/managed-settings.json";
|
|
2463
|
+
var finding10 = (subject, verdict, message) => ({
|
|
2464
|
+
check: "rails",
|
|
2465
|
+
message,
|
|
2466
|
+
subject,
|
|
2467
|
+
verdict
|
|
2468
|
+
});
|
|
2469
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2470
|
+
var networkIn = (parsed) => {
|
|
2471
|
+
if (!isRecord3(parsed)) return void 0;
|
|
2472
|
+
const sandbox = parsed["sandbox"];
|
|
2473
|
+
if (!isRecord3(sandbox)) return void 0;
|
|
2474
|
+
const network = sandbox["network"];
|
|
2475
|
+
if (!isRecord3(network)) return void 0;
|
|
2476
|
+
const allowed = network["allowedDomains"];
|
|
2477
|
+
return {
|
|
2478
|
+
allowedDomains: Array.isArray(allowed) ? allowed.filter((one) => typeof one === "string") : [],
|
|
2479
|
+
...network["allowManagedDomainsOnly"] === true ? { allowManagedDomainsOnly: true } : {}
|
|
2480
|
+
};
|
|
2481
|
+
};
|
|
2482
|
+
var sourceAt = (path, label) => {
|
|
2483
|
+
if (!existsSync10(path)) return { network: void 0, path: label, unreadable: false };
|
|
2484
|
+
try {
|
|
2485
|
+
return {
|
|
2486
|
+
network: networkIn(JSON.parse(readFileSync13(path, "utf8"))),
|
|
2487
|
+
path: label,
|
|
2488
|
+
unreadable: false
|
|
2489
|
+
};
|
|
2490
|
+
} catch {
|
|
2491
|
+
return { network: void 0, path: label, unreadable: true };
|
|
2492
|
+
}
|
|
2493
|
+
};
|
|
2494
|
+
var declaredEgress = (root) => {
|
|
2495
|
+
const at = join13(root, GEONOSIS2);
|
|
2496
|
+
if (!existsSync10(at)) return void 0;
|
|
2497
|
+
let parsed;
|
|
2498
|
+
try {
|
|
2499
|
+
parsed = JSON.parse(readFileSync13(at, "utf8"));
|
|
2500
|
+
} catch {
|
|
2501
|
+
return void 0;
|
|
2502
|
+
}
|
|
2503
|
+
const egress = parsed?.rails?.egress;
|
|
2504
|
+
if (!isRecord3(egress)) return void 0;
|
|
2505
|
+
const allow = egress["allow"];
|
|
2506
|
+
return {
|
|
2507
|
+
allow: Array.isArray(allow) ? allow.filter((one) => typeof one === "string") : []
|
|
2508
|
+
};
|
|
2509
|
+
};
|
|
2510
|
+
var deniedEgress = (root) => {
|
|
2511
|
+
const at = join13(root, RUN_RECORD);
|
|
2512
|
+
if (!existsSync10(at)) {
|
|
2513
|
+
return [
|
|
2514
|
+
finding10(
|
|
2515
|
+
"deniedEgress",
|
|
2516
|
+
"SKIP",
|
|
2517
|
+
`there is no ${RUN_RECORD} here, so no run has been recorded for this to be a gate over`
|
|
2518
|
+
)
|
|
2519
|
+
];
|
|
2520
|
+
}
|
|
2521
|
+
let parsed;
|
|
2522
|
+
try {
|
|
2523
|
+
parsed = JSON.parse(readFileSync13(at, "utf8"));
|
|
2524
|
+
} catch (error) {
|
|
2525
|
+
return [
|
|
2526
|
+
finding10(
|
|
2527
|
+
"deniedEgress",
|
|
2528
|
+
"FAIL",
|
|
2529
|
+
`${RUN_RECORD} is not readable JSON (${error.message}) \u2014 a record nobody can read is not a record of zero denied attempts`
|
|
2530
|
+
)
|
|
2531
|
+
];
|
|
2532
|
+
}
|
|
2533
|
+
const denied = isRecord3(parsed) && Array.isArray(parsed["deniedEgress"]) ? parsed["deniedEgress"] : [];
|
|
2534
|
+
if (denied.length === 0) {
|
|
2535
|
+
return [finding10("deniedEgress", "OK", "no run recorded a denied egress attempt")];
|
|
2536
|
+
}
|
|
2537
|
+
const hosts = denied.map((one) => isRecord3(one) && typeof one["host"] === "string" ? one["host"] : "(unnamed)").join(", ");
|
|
2538
|
+
return [
|
|
2539
|
+
finding10(
|
|
2540
|
+
"deniedEgress",
|
|
2541
|
+
"FAIL",
|
|
2542
|
+
`${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`
|
|
2543
|
+
)
|
|
2544
|
+
];
|
|
2545
|
+
};
|
|
2546
|
+
var checkRails = ({
|
|
2547
|
+
managedSettings = managedSettingsPath(),
|
|
2548
|
+
root,
|
|
2549
|
+
userSettings
|
|
2550
|
+
}) => {
|
|
2551
|
+
const declared = declaredEgress(root);
|
|
2552
|
+
if (declared === void 0 || declared.allow.length === 0) {
|
|
2553
|
+
return [
|
|
2554
|
+
finding10(
|
|
2555
|
+
`${GEONOSIS2} \u2192 rails.egress`,
|
|
2556
|
+
"SKIP",
|
|
2557
|
+
"this repo declares no egress allowlist, so there is no rendered setting for this to read back"
|
|
2558
|
+
)
|
|
2559
|
+
];
|
|
2560
|
+
}
|
|
2561
|
+
const sources = [
|
|
2562
|
+
sourceAt(managedSettings, managedSettings),
|
|
2563
|
+
sourceAt(userSettings, userSettings),
|
|
2564
|
+
sourceAt(join13(root, PROJECT), PROJECT),
|
|
2565
|
+
sourceAt(join13(root, LOCAL), LOCAL)
|
|
2566
|
+
];
|
|
2567
|
+
const unreadable = sources.filter((one) => one.unreadable).map(
|
|
2568
|
+
(one) => finding10(
|
|
2569
|
+
one.path,
|
|
2570
|
+
"FAIL",
|
|
2571
|
+
"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"
|
|
2572
|
+
)
|
|
2573
|
+
);
|
|
2574
|
+
const managedNetwork = sources[0]?.network;
|
|
2575
|
+
const onlyManaged = managedNetwork?.allowManagedDomainsOnly === true;
|
|
2576
|
+
const effective = new Set(
|
|
2577
|
+
(onlyManaged ? [sources[0]] : sources).flatMap((one) => one?.network?.allowedDomains ?? [])
|
|
2578
|
+
);
|
|
2579
|
+
const why = onlyManaged ? ` Managed settings set sandbox.network.allowManagedDomainsOnly, so only ${managedSettings} allow rules load and every project, user and local domain is ignored \u2014 this one has to be added THERE` : ` Render it with \`geonosis-rails egress --write\`, into ${PROJECT} or the user scope`;
|
|
2580
|
+
return [
|
|
2581
|
+
...unreadable,
|
|
2582
|
+
...declared.allow.map(
|
|
2583
|
+
(domain) => effective.has(domain) ? finding10(domain, "OK", "declared, and in the settings that actually load") : finding10(
|
|
2584
|
+
domain,
|
|
2585
|
+
"FAIL",
|
|
2586
|
+
`declared in ${GEONOSIS2} \u2192 rails.egress and absent from every settings file that loads, so the run has no allowance for it.${why}`
|
|
2587
|
+
)
|
|
2588
|
+
),
|
|
2589
|
+
...deniedEgress(root)
|
|
2590
|
+
];
|
|
2591
|
+
};
|
|
2592
|
+
|
|
2593
|
+
// src/required-options.ts
|
|
2594
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2595
|
+
var HEADER = /requires option `([^`]+)`/;
|
|
2596
|
+
var optionsOf2 = (level) => Array.isArray(level) ? level.slice(1) : [];
|
|
2597
|
+
var missingOption = (error) => {
|
|
2598
|
+
const named2 = error.option;
|
|
2599
|
+
return typeof named2 === "string" ? named2 : HEADER.exec(String(error.message))?.[1];
|
|
2600
|
+
};
|
|
2601
|
+
var refusesBare = (rule) => {
|
|
2602
|
+
try {
|
|
2603
|
+
rule.create({ options: [], report: () => {
|
|
2604
|
+
} });
|
|
2605
|
+
return false;
|
|
2606
|
+
} catch (error) {
|
|
2607
|
+
return missingOption(error) !== void 0;
|
|
2608
|
+
}
|
|
2609
|
+
};
|
|
2610
|
+
var configured = (config, namespace) => [config.rules, ...config.overrides.map((one) => one.rules)].flatMap(
|
|
2611
|
+
(rules) => enabledRulesOf(rules, namespace).map((id) => [id, optionsOf2(rules[id])])
|
|
2612
|
+
);
|
|
2613
|
+
var checkRequiredOptions = async ({
|
|
2614
|
+
config,
|
|
2615
|
+
entry
|
|
2616
|
+
}) => {
|
|
2617
|
+
const loaded = await import(pathToFileURL2(entry).href).catch(() => void 0);
|
|
2618
|
+
const namespace = loaded?.default?.meta?.name;
|
|
2619
|
+
const rules = loaded?.default?.rules;
|
|
2620
|
+
if (typeof namespace !== "string" || namespace === "" || rules === void 0) return [];
|
|
2621
|
+
const enabled = configured(config, namespace);
|
|
2622
|
+
if (enabled.length === 0) return [];
|
|
2623
|
+
const missing = [];
|
|
2624
|
+
let gated = 0;
|
|
2625
|
+
for (const [id, options] of enabled) {
|
|
2626
|
+
const rule = rules[id.slice(namespace.length + 1)];
|
|
2627
|
+
if (rule === void 0) continue;
|
|
2628
|
+
if (refusesBare(rule)) gated += 1;
|
|
2629
|
+
try {
|
|
2630
|
+
rule.create({ options, report: () => {
|
|
2631
|
+
} });
|
|
2632
|
+
} catch (error) {
|
|
2633
|
+
const option = missingOption(error);
|
|
2634
|
+
if (option === void 0) continue;
|
|
2635
|
+
missing.push({
|
|
2636
|
+
check: "loaded",
|
|
2637
|
+
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`,
|
|
2638
|
+
subject: `${config.relative} \u2192 ${id}`,
|
|
2639
|
+
verdict: "FAIL"
|
|
2640
|
+
});
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
if (missing.length > 0) return missing;
|
|
2644
|
+
return [
|
|
2645
|
+
{
|
|
2646
|
+
check: "loaded",
|
|
2647
|
+
message: `${gated} rules require options, ${gated} carry them`,
|
|
2648
|
+
subject: config.relative,
|
|
2649
|
+
verdict: "OK"
|
|
2650
|
+
}
|
|
2651
|
+
];
|
|
2652
|
+
};
|
|
2653
|
+
|
|
2654
|
+
// src/doctor.ts
|
|
1894
2655
|
var exercisedOf = ({
|
|
1895
2656
|
configs,
|
|
1896
2657
|
oxlint,
|
|
1897
2658
|
repoCorpus,
|
|
1898
|
-
root
|
|
2659
|
+
root,
|
|
2660
|
+
workspaces
|
|
1899
2661
|
}) => Promise.all(
|
|
1900
2662
|
configs.flatMap(
|
|
1901
2663
|
(config) => config.jsPlugins.filter((name) => name.startsWith(SCOPE)).map(async (specifier) => {
|
|
@@ -1905,25 +2667,49 @@ var exercisedOf = ({
|
|
|
1905
2667
|
corpus = corpusOfPlugin(config.dir, specifier);
|
|
1906
2668
|
entry = resolveFrom(config.dir, specifier);
|
|
1907
2669
|
} catch (error) {
|
|
1908
|
-
return
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
2670
|
+
return [
|
|
2671
|
+
{
|
|
2672
|
+
check: "exercised",
|
|
2673
|
+
message: `${specifier}: ${String(error.message)}`,
|
|
2674
|
+
subject: config.relative,
|
|
2675
|
+
verdict: "SKIP"
|
|
2676
|
+
}
|
|
2677
|
+
];
|
|
1914
2678
|
}
|
|
1915
2679
|
const own = repoCorpus !== void 0 && config.relative === CONFIG_FILE ? repoCorpus : void 0;
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
2680
|
+
const [reach, engines] = await Promise.all([
|
|
2681
|
+
checkExercised({
|
|
2682
|
+
config,
|
|
2683
|
+
corpus,
|
|
2684
|
+
entry,
|
|
2685
|
+
oxlint,
|
|
2686
|
+
...own === void 0 ? {} : { repoCorpus: own },
|
|
2687
|
+
root
|
|
2688
|
+
}),
|
|
2689
|
+
// #159: the rules whose engine this tree has not got. Beside the reach line rather than
|
|
2690
|
+
// inside it — one is about the corpus, the other about this repo's own manifests.
|
|
2691
|
+
checkEngines({ config, entry, workspaces })
|
|
2692
|
+
]);
|
|
2693
|
+
return [reach, ...engines];
|
|
1924
2694
|
})
|
|
1925
2695
|
)
|
|
1926
2696
|
);
|
|
2697
|
+
var requiredOptionsOf = async (configs) => {
|
|
2698
|
+
const found = await Promise.all(
|
|
2699
|
+
configs.flatMap(
|
|
2700
|
+
(config) => config.jsPlugins.filter((name) => name.startsWith(SCOPE)).map(async (specifier) => {
|
|
2701
|
+
let entry;
|
|
2702
|
+
try {
|
|
2703
|
+
entry = resolveFrom(config.dir, specifier);
|
|
2704
|
+
} catch {
|
|
2705
|
+
return [];
|
|
2706
|
+
}
|
|
2707
|
+
return checkRequiredOptions({ config, entry });
|
|
2708
|
+
})
|
|
2709
|
+
)
|
|
2710
|
+
);
|
|
2711
|
+
return found.flat();
|
|
2712
|
+
};
|
|
1927
2713
|
var skip = (message) => [
|
|
1928
2714
|
{ check: "baseline", message, subject: RATCHET_FILE, verdict: "SKIP" }
|
|
1929
2715
|
];
|
|
@@ -1939,6 +2725,7 @@ var baselineOf = ({
|
|
|
1939
2725
|
const ref = baseline.ref ?? defaultRef(root);
|
|
1940
2726
|
return ref === void 0 ? skip("no ref was named and this repo has no origin/main to fall back on") : checkBaseline({ ref, root });
|
|
1941
2727
|
};
|
|
2728
|
+
var USER_SETTINGS = ".claude/settings.json";
|
|
1942
2729
|
var ordered = (findings) => CHECKS.flatMap((check) => findings.filter((one) => one.check === check));
|
|
1943
2730
|
var EMPTY = { FAIL: 0, OK: 0, SKIP: 0, UNJUDGED: 0, WARN: 0 };
|
|
1944
2731
|
var countsOf = (findings) => findings.reduce((counts, one) => ({ ...counts, [one.verdict]: counts[one.verdict] + 1 }), {
|
|
@@ -1960,7 +2747,24 @@ var runDoctor = async ({
|
|
|
1960
2747
|
}
|
|
1961
2748
|
const asked = (check) => only === void 0 || only.includes(check);
|
|
1962
2749
|
const runs = [
|
|
1963
|
-
[
|
|
2750
|
+
[
|
|
2751
|
+
"loaded",
|
|
2752
|
+
async () => [
|
|
2753
|
+
...await checkLoaded({ configs, root, workspaces }),
|
|
2754
|
+
...await requiredOptionsOf(configs)
|
|
2755
|
+
]
|
|
2756
|
+
],
|
|
2757
|
+
[
|
|
2758
|
+
"group",
|
|
2759
|
+
() => {
|
|
2760
|
+
const declared = declaredGroupsOf(root);
|
|
2761
|
+
return checkGroup({
|
|
2762
|
+
...declared === void 0 ? {} : { groups: declared },
|
|
2763
|
+
root,
|
|
2764
|
+
workspaces
|
|
2765
|
+
});
|
|
2766
|
+
}
|
|
2767
|
+
],
|
|
1964
2768
|
[
|
|
1965
2769
|
"exercised",
|
|
1966
2770
|
() => {
|
|
@@ -1969,25 +2773,33 @@ var runDoctor = async ({
|
|
|
1969
2773
|
configs,
|
|
1970
2774
|
oxlint: oxlint ?? resolveOxlint(root),
|
|
1971
2775
|
...repoCorpus === void 0 ? {} : { repoCorpus },
|
|
1972
|
-
root
|
|
1973
|
-
|
|
2776
|
+
root,
|
|
2777
|
+
workspaces
|
|
2778
|
+
}).then((found2) => found2.flat());
|
|
1974
2779
|
}
|
|
1975
2780
|
],
|
|
1976
2781
|
["baseline", () => baselineOf({ baseline, root })],
|
|
1977
|
-
["runner", () => checkRunner({ ratchet: readRatchet(root), workspaces })],
|
|
2782
|
+
["runner", () => checkRunner({ ratchet: readRatchet(root), root, workspaces })],
|
|
1978
2783
|
["envelope", () => checkEnvelopes({ root })],
|
|
1979
2784
|
["observability", () => checkObservability({ now: Date.now(), root })],
|
|
1980
2785
|
["drift", () => checkDrift({ root, workspaces })],
|
|
1981
|
-
["deployed", () => checkDeployed({ root })]
|
|
2786
|
+
["deployed", () => checkDeployed({ root })],
|
|
2787
|
+
["rails", () => checkRails({ root, userSettings: join14(homedir2(), USER_SETTINGS) })]
|
|
1982
2788
|
];
|
|
1983
2789
|
const collected = [];
|
|
1984
|
-
|
|
2790
|
+
const ran = [];
|
|
2791
|
+
for (const [check, run] of runs) {
|
|
2792
|
+
if (!asked(check)) continue;
|
|
2793
|
+
ran.push(check);
|
|
2794
|
+
collected.push(...await run());
|
|
2795
|
+
}
|
|
1985
2796
|
const found = ordered(collected);
|
|
1986
2797
|
const findings = strict ? found.map((one) => one.verdict === "WARN" ? { ...one, verdict: "FAIL" } : one) : found;
|
|
1987
2798
|
return {
|
|
1988
2799
|
counts: countsOf(findings),
|
|
1989
2800
|
findings,
|
|
1990
2801
|
ok: passes(findings),
|
|
2802
|
+
ran,
|
|
1991
2803
|
root
|
|
1992
2804
|
};
|
|
1993
2805
|
};
|
|
@@ -1999,8 +2811,10 @@ var ABOUT = {
|
|
|
1999
2811
|
drift: "the gates that were set up and are no longer running",
|
|
2000
2812
|
envelope: "every gate read as many things as it was handed",
|
|
2001
2813
|
exercised: "every enabled rule fires on at least one corpus file",
|
|
2814
|
+
group: "the packages published on one version resolve to one version",
|
|
2002
2815
|
loaded: "the plugin oxlint would load is the one the manifest pins",
|
|
2003
2816
|
observability: "an exporter is configured, reachable, and something arrived through it lately",
|
|
2817
|
+
rails: "the egress a run is bounded by is in the settings that actually load",
|
|
2004
2818
|
runner: "something reads the test runner\u2019s own report, not its exit code"
|
|
2005
2819
|
};
|
|
2006
2820
|
var WIDTH = 8;
|
|
@@ -2042,6 +2856,8 @@ export {
|
|
|
2042
2856
|
DEPLOYED_FILE,
|
|
2043
2857
|
NOT_WRITTEN,
|
|
2044
2858
|
checkDeployed,
|
|
2859
|
+
GEONOSIS_FILE,
|
|
2860
|
+
repoCorpusOf,
|
|
2045
2861
|
CHECKS,
|
|
2046
2862
|
DoctorError,
|
|
2047
2863
|
resolveFrom,
|
|
@@ -2049,20 +2865,24 @@ export {
|
|
|
2049
2865
|
pluginVersionOf,
|
|
2050
2866
|
corpusOfPlugin,
|
|
2051
2867
|
relativeToRoot,
|
|
2868
|
+
FIXED_GROUP,
|
|
2869
|
+
KIT_GROUP,
|
|
2870
|
+
declaredGroupsOf,
|
|
2871
|
+
checkGroup,
|
|
2872
|
+
COMPOSITION_ROOT,
|
|
2873
|
+
FLOOR_PACKAGES,
|
|
2052
2874
|
READERS,
|
|
2053
2875
|
checkDrift,
|
|
2876
|
+
enabledRulesOf,
|
|
2877
|
+
checkExercised,
|
|
2054
2878
|
ENVELOPES_DIR,
|
|
2055
2879
|
NO_ENVELOPES,
|
|
2056
2880
|
checkEnvelopes,
|
|
2057
|
-
enabledRulesOf,
|
|
2058
|
-
checkExercised,
|
|
2059
2881
|
SCOPE,
|
|
2060
2882
|
satisfies,
|
|
2061
2883
|
declaredFor,
|
|
2062
2884
|
checkLoaded,
|
|
2063
2885
|
checkObservability,
|
|
2064
|
-
GEONOSIS_FILE2 as GEONOSIS_FILE,
|
|
2065
|
-
repoCorpusOf,
|
|
2066
2886
|
checkRunner,
|
|
2067
2887
|
runDoctor,
|
|
2068
2888
|
formatDoctor,
|