@geonosis/doctor 1.3.0 → 2.0.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 +57 -14
- package/dist/{chunk-7BRGHYQR.js → chunk-SMCRXSH5.js} +1122 -251
- package/dist/doctor-cli.js +60 -9
- package/dist/index.d.ts +83 -6
- package/dist/index.js +19 -1
- package/package.json +2 -2
|
@@ -322,12 +322,15 @@ var checkDeployed = ({ root }) => {
|
|
|
322
322
|
// src/types.ts
|
|
323
323
|
var CHECKS = [
|
|
324
324
|
"loaded",
|
|
325
|
+
"group",
|
|
325
326
|
"exercised",
|
|
326
327
|
"baseline",
|
|
327
328
|
"runner",
|
|
329
|
+
"envelope",
|
|
328
330
|
"drift",
|
|
329
331
|
"observability",
|
|
330
|
-
"deployed"
|
|
332
|
+
"deployed",
|
|
333
|
+
"rails"
|
|
331
334
|
];
|
|
332
335
|
var DoctorError = class extends Error {
|
|
333
336
|
constructor(message) {
|
|
@@ -404,6 +407,20 @@ var probesOf = async (entry, plugin) => {
|
|
|
404
407
|
})
|
|
405
408
|
);
|
|
406
409
|
};
|
|
410
|
+
var presumptionsOf = async (entry) => {
|
|
411
|
+
const loaded = await import(pathToFileURL(entry).href);
|
|
412
|
+
const namespace = loaded.default?.meta?.name;
|
|
413
|
+
return {
|
|
414
|
+
namespace: typeof namespace === "string" ? namespace : "",
|
|
415
|
+
presumed: Object.fromEntries(
|
|
416
|
+
Object.entries(loaded.default?.rules ?? {}).flatMap(([name, rule]) => {
|
|
417
|
+
const presumes = rule?.presumes;
|
|
418
|
+
const engine = presumes?.engine;
|
|
419
|
+
return typeof engine === "string" && Array.isArray(presumes?.packages) ? [[name, { engine, packages: presumes.packages }]] : [];
|
|
420
|
+
})
|
|
421
|
+
)
|
|
422
|
+
};
|
|
423
|
+
};
|
|
407
424
|
var corpusOfPlugin = (from, specifier) => join3(packageDirOf(resolveFrom(from, specifier), specifier), "corpus");
|
|
408
425
|
var real = (path) => {
|
|
409
426
|
try {
|
|
@@ -415,9 +432,40 @@ var real = (path) => {
|
|
|
415
432
|
var relativeToRoot = (root, path) => relativePath(real(root), real(path));
|
|
416
433
|
|
|
417
434
|
// src/drift.ts
|
|
418
|
-
import { closeSync, existsSync as
|
|
435
|
+
import { closeSync, existsSync as existsSync4, openSync, readdirSync as readdirSync3, readFileSync as readFileSync5, readSync } from "fs";
|
|
419
436
|
import { homedir } from "os";
|
|
420
|
-
import { join as
|
|
437
|
+
import { join as join5, sep as sep2 } from "path";
|
|
438
|
+
|
|
439
|
+
// src/hooks.ts
|
|
440
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
|
|
441
|
+
import { join as join4 } from "path";
|
|
442
|
+
var HOOK_FILES = ["lefthook.yml", "lefthook.yaml", ".lefthook.yml", ".lefthook.yaml"];
|
|
443
|
+
var HOOK_DIRS = [".husky", ".githooks"];
|
|
444
|
+
var VERSION_MANAGED = /(?:^|[\s;&|(])(?<runner>pnpm|npx|bunx|yarn)\b(?:\s+(?:exec|run|dlx|x))?\s+(?<bin>geonosis(?:-[a-z-]+)?)\b/;
|
|
445
|
+
var NAMES_A_BIN = /(?:^|[\s;&|(/])geonosis(?:-[a-z-]+)?\b/;
|
|
446
|
+
var hookLines = (root) => {
|
|
447
|
+
const found = [];
|
|
448
|
+
const read = (path) => {
|
|
449
|
+
const at = relativePath(root, path);
|
|
450
|
+
for (const line of readFileSync4(path, "utf8").split("\n")) {
|
|
451
|
+
if (line.trim() !== "") found.push({ at, line });
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
for (const name of HOOK_FILES) {
|
|
455
|
+
const path = join4(root, name);
|
|
456
|
+
if (existsSync3(path)) read(path);
|
|
457
|
+
}
|
|
458
|
+
for (const name of HOOK_DIRS) {
|
|
459
|
+
const dir = join4(root, name);
|
|
460
|
+
if (!existsSync3(dir)) continue;
|
|
461
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
462
|
+
if (entry.isFile() && !entry.name.startsWith("_") && !entry.name.startsWith(".")) {
|
|
463
|
+
read(join4(dir, entry.name));
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return found;
|
|
468
|
+
};
|
|
421
469
|
|
|
422
470
|
// src/overrides.ts
|
|
423
471
|
var SPECIAL = /* @__PURE__ */ new Set(["$", "(", ")", "+", ".", "/", "@", "\\", "^", "|"]);
|
|
@@ -501,29 +549,29 @@ var filesUnder = (dir, match) => {
|
|
|
501
549
|
const walk2 = (at) => {
|
|
502
550
|
let entries;
|
|
503
551
|
try {
|
|
504
|
-
entries =
|
|
552
|
+
entries = readdirSync3(at, { withFileTypes: true });
|
|
505
553
|
} catch {
|
|
506
554
|
return;
|
|
507
555
|
}
|
|
508
556
|
for (const entry of entries) {
|
|
509
557
|
if (entry.isDirectory()) {
|
|
510
|
-
if (!entry.name.startsWith(".") && !NEVER_WALKED2.has(entry.name)) walk2(
|
|
558
|
+
if (!entry.name.startsWith(".") && !NEVER_WALKED2.has(entry.name)) walk2(join5(at, entry.name));
|
|
511
559
|
continue;
|
|
512
560
|
}
|
|
513
|
-
if (entry.isFile() && match(entry.name)) found.push(
|
|
561
|
+
if (entry.isFile() && match(entry.name)) found.push(join5(at, entry.name));
|
|
514
562
|
}
|
|
515
563
|
};
|
|
516
564
|
walk2(dir);
|
|
517
565
|
return found;
|
|
518
566
|
};
|
|
519
567
|
var ci = (root) => {
|
|
520
|
-
const dir =
|
|
521
|
-
if (!
|
|
568
|
+
const dir = join5(root, WORKFLOWS);
|
|
569
|
+
if (!existsSync4(dir)) {
|
|
522
570
|
return [finding3(WORKFLOWS, "SKIP", "there are no workflows here to read")];
|
|
523
571
|
}
|
|
524
572
|
return filesUnder(dir, (name) => name.endsWith(".yml") || name.endsWith(".yaml")).map((path) => {
|
|
525
573
|
const at = relativePath(root, path);
|
|
526
|
-
return SWITCHED_OFF.test(
|
|
574
|
+
return SWITCHED_OFF.test(readFileSync5(path, "utf8")) ? finding3(
|
|
527
575
|
at,
|
|
528
576
|
"FAIL",
|
|
529
577
|
"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"
|
|
@@ -571,7 +619,7 @@ var pathsRunByScript = (script) => script.split(BETWEEN_COMMANDS).map(pathRunBy)
|
|
|
571
619
|
var scriptPaths = (workspaces) => {
|
|
572
620
|
const missing = workspaces.flatMap(
|
|
573
621
|
(one) => Object.entries(one.manifest.scripts ?? {}).flatMap(
|
|
574
|
-
([name, script]) => pathsRunByScript(script).filter((path) => !
|
|
622
|
+
([name, script]) => pathsRunByScript(script).filter((path) => !existsSync4(join5(one.dir, path))).map((path) => ({ name, path, workspace: one }))
|
|
575
623
|
)
|
|
576
624
|
);
|
|
577
625
|
if (missing.length === 0) {
|
|
@@ -607,7 +655,7 @@ var linkedBins = (root, workspaces) => {
|
|
|
607
655
|
}
|
|
608
656
|
const missing = workspaces.flatMap(
|
|
609
657
|
(one) => Object.entries(one.manifest.scripts ?? {}).flatMap(
|
|
610
|
-
([script, body]) => [...new Set(body.split(/[\s;|&()]+/).filter((word) => bins.has(word)))].filter((name) => !
|
|
658
|
+
([script, body]) => [...new Set(body.split(/[\s;|&()]+/).filter((word) => bins.has(word)))].filter((name) => !existsSync4(join5(root, "node_modules/.bin", name))).map((name) => ({ name, script, workspace: one }))
|
|
611
659
|
)
|
|
612
660
|
);
|
|
613
661
|
if (missing.length === 0) {
|
|
@@ -678,11 +726,11 @@ var everythingRun = (root, workspaces) => {
|
|
|
678
726
|
const scripts = workspaces.flatMap((one) => Object.values(one.manifest.scripts ?? {}));
|
|
679
727
|
const counters = (readRatchet(root)?.counters ?? []).map((entry) => String(entry.command ?? ""));
|
|
680
728
|
const workflows = filesUnder(
|
|
681
|
-
|
|
729
|
+
join5(root, WORKFLOWS),
|
|
682
730
|
(name) => name.endsWith(".yml") || name.endsWith(".yaml")
|
|
683
731
|
).map((path) => {
|
|
684
732
|
try {
|
|
685
|
-
return
|
|
733
|
+
return readFileSync5(path, "utf8");
|
|
686
734
|
} catch {
|
|
687
735
|
return "";
|
|
688
736
|
}
|
|
@@ -718,10 +766,10 @@ var generatedFiles = (root, workspaces) => {
|
|
|
718
766
|
});
|
|
719
767
|
};
|
|
720
768
|
var readGeonosis = (root) => {
|
|
721
|
-
const path =
|
|
722
|
-
if (!
|
|
769
|
+
const path = join5(root, GEONOSIS);
|
|
770
|
+
if (!existsSync4(path)) return void 0;
|
|
723
771
|
try {
|
|
724
|
-
return JSON.parse(
|
|
772
|
+
return JSON.parse(readFileSync5(path, "utf8"));
|
|
725
773
|
} catch {
|
|
726
774
|
return void 0;
|
|
727
775
|
}
|
|
@@ -729,11 +777,11 @@ var readGeonosis = (root) => {
|
|
|
729
777
|
var law = (root, config) => {
|
|
730
778
|
const declared = config?.law ?? {};
|
|
731
779
|
const file = typeof declared.file === "string" ? declared.file : LAW;
|
|
732
|
-
const path =
|
|
733
|
-
if (!
|
|
780
|
+
const path = join5(root, file);
|
|
781
|
+
if (!existsSync4(path)) {
|
|
734
782
|
return [finding3(file, "SKIP", "there is no law file here to measure")];
|
|
735
783
|
}
|
|
736
|
-
const source =
|
|
784
|
+
const source = readFileSync5(path, "utf8");
|
|
737
785
|
const lines = source.split("\n").length - (source.endsWith("\n") ? 1 : 0);
|
|
738
786
|
if (typeof declared.maxLines !== "number") {
|
|
739
787
|
return [
|
|
@@ -755,9 +803,9 @@ var law = (root, config) => {
|
|
|
755
803
|
};
|
|
756
804
|
var KIT_PLUGIN = "geonosis";
|
|
757
805
|
var enablesKit = (path) => {
|
|
758
|
-
if (!
|
|
806
|
+
if (!existsSync4(path)) return false;
|
|
759
807
|
try {
|
|
760
|
-
const parsed = JSON.parse(
|
|
808
|
+
const parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
761
809
|
const enabled = parsed.enabledPlugins;
|
|
762
810
|
if (typeof enabled !== "object" || enabled === null) return false;
|
|
763
811
|
return Object.entries(enabled).some(
|
|
@@ -768,7 +816,7 @@ var enablesKit = (path) => {
|
|
|
768
816
|
}
|
|
769
817
|
};
|
|
770
818
|
var hooks = (root, userSettings) => {
|
|
771
|
-
const path =
|
|
819
|
+
const path = join5(root, SETTINGS);
|
|
772
820
|
if (enablesKit(path)) {
|
|
773
821
|
return [finding3(SETTINGS, "OK", `\`enabledPlugins\` here enables the kit\u2019s plugin`)];
|
|
774
822
|
}
|
|
@@ -789,6 +837,160 @@ var hooks = (root, userSettings) => {
|
|
|
789
837
|
)
|
|
790
838
|
];
|
|
791
839
|
};
|
|
840
|
+
var ALLOW_BUILDS_KEY = "allowBuilds";
|
|
841
|
+
var allowedBuilds = (root) => {
|
|
842
|
+
const path = join5(root, WORKSPACE_YAML);
|
|
843
|
+
if (!existsSync4(path)) return void 0;
|
|
844
|
+
const lines = readFileSync5(path, "utf8").split("\n");
|
|
845
|
+
const at = lines.findIndex((line) => new RegExp(`^${ALLOW_BUILDS_KEY}\\s*:`).test(line));
|
|
846
|
+
if (at < 0) return void 0;
|
|
847
|
+
const names = [];
|
|
848
|
+
for (const line of lines.slice(at + 1)) {
|
|
849
|
+
if (/^\s*(?:#.*)?$/.test(line)) continue;
|
|
850
|
+
const entry = /^\s+(?<name>'[^']+'|"[^"]+"|[^:\s]+)\s*:/.exec(line);
|
|
851
|
+
const name = entry?.groups?.["name"];
|
|
852
|
+
if (name === void 0) break;
|
|
853
|
+
names.push(name.replace(/^['"]|['"]$/g, ""));
|
|
854
|
+
}
|
|
855
|
+
return names;
|
|
856
|
+
};
|
|
857
|
+
var PLATFORM = /(?:^|[-/])(?:darwin|linux|win32|windows|freebsd|openbsd|android|sunos)(?:[-.]|$)|(?:^|[-/])(?:arm64|x64|ia32|ppc64|s390x|riscv64)(?:[-.]|$)/;
|
|
858
|
+
var familyOf = (name) => name.split("/").slice(0, -1).join("/") || name;
|
|
859
|
+
var platformSplitBuilds = (root) => {
|
|
860
|
+
const names = allowedBuilds(root);
|
|
861
|
+
if (names === void 0 || names.length === 0) return [];
|
|
862
|
+
const families = /* @__PURE__ */ new Map();
|
|
863
|
+
for (const name of names.filter((one) => PLATFORM.test(one))) {
|
|
864
|
+
const family = familyOf(name);
|
|
865
|
+
families.set(family, [...families.get(family) ?? [], name]);
|
|
866
|
+
}
|
|
867
|
+
return [...families.entries()].flatMap(
|
|
868
|
+
([family, allowed]) => allowed.length > 1 ? [] : [
|
|
869
|
+
finding3(
|
|
870
|
+
allowed[0] ?? family,
|
|
871
|
+
"WARN",
|
|
872
|
+
`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`
|
|
873
|
+
)
|
|
874
|
+
]
|
|
875
|
+
);
|
|
876
|
+
};
|
|
877
|
+
var LINTS_STAGED = /\b(?:oxlint|eslint|biome)\b[^\n]*(?:\{staged_files\}|\{files\}|\$\{?[@*]|"\$@")/;
|
|
878
|
+
var TAKES_A_VALUE = /* @__PURE__ */ new Set([
|
|
879
|
+
"--config",
|
|
880
|
+
"--format",
|
|
881
|
+
"--ignore-path",
|
|
882
|
+
"--max-warnings",
|
|
883
|
+
"--reporter",
|
|
884
|
+
"--rulesdir",
|
|
885
|
+
"--tsconfig",
|
|
886
|
+
"-c"
|
|
887
|
+
]);
|
|
888
|
+
var pathsLintedBy = (script, dir) => {
|
|
889
|
+
const words = script.split(/\s+/).filter((word) => word !== "");
|
|
890
|
+
const named2 = [];
|
|
891
|
+
for (const [at, word] of words.entries()) {
|
|
892
|
+
if (word.startsWith("-")) continue;
|
|
893
|
+
const before = words[at - 1] ?? "";
|
|
894
|
+
if (TAKES_A_VALUE.has(before)) continue;
|
|
895
|
+
if (existsSync4(join5(dir, word))) named2.push(word.replace(/^\.\//, "").replace(/\/$/, ""));
|
|
896
|
+
}
|
|
897
|
+
return named2;
|
|
898
|
+
};
|
|
899
|
+
var LINTABLE = /\.[cm]?[jt]sx?$/;
|
|
900
|
+
var sourceDirsIn = (dir) => {
|
|
901
|
+
let entries;
|
|
902
|
+
try {
|
|
903
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
904
|
+
} catch {
|
|
905
|
+
return [];
|
|
906
|
+
}
|
|
907
|
+
return entries.filter(
|
|
908
|
+
(entry) => entry.isDirectory() && !entry.name.startsWith(".") && !NEVER_WALKED2.has(entry.name) && filesUnder(join5(dir, entry.name), (name) => LINTABLE.test(name)).length > 0
|
|
909
|
+
).map((entry) => entry.name);
|
|
910
|
+
};
|
|
911
|
+
var lintScopes = (root, workspaces) => {
|
|
912
|
+
if (!hookLines(root).some((one) => LINTS_STAGED.test(one.line))) {
|
|
913
|
+
return [
|
|
914
|
+
finding3(
|
|
915
|
+
"lint scope",
|
|
916
|
+
"SKIP",
|
|
917
|
+
"no committed hook lints staged files here, so there is no second file set for a lint script to be narrower than"
|
|
918
|
+
)
|
|
919
|
+
];
|
|
920
|
+
}
|
|
921
|
+
return workspaces.flatMap((workspace) => {
|
|
922
|
+
const script = workspace.manifest.scripts?.lint;
|
|
923
|
+
if (typeof script !== "string" || script.trim() === "") return [];
|
|
924
|
+
const at = workspace.relative === "" ? "package.json" : `${workspace.relative}/package.json`;
|
|
925
|
+
const covered = pathsLintedBy(script, workspace.dir);
|
|
926
|
+
if (covered.length === 0 || covered.includes(".")) {
|
|
927
|
+
return [finding3(at, "OK", `"${script}" covers everything the hook could stage here`)];
|
|
928
|
+
}
|
|
929
|
+
const uncovered = sourceDirsIn(workspace.dir).filter(
|
|
930
|
+
(name) => !covered.some((path) => name === path || path.startsWith(`${name}/`))
|
|
931
|
+
);
|
|
932
|
+
if (uncovered.length === 0) {
|
|
933
|
+
return [finding3(at, "OK", `"${script}" covers every source directory under this workspace`)];
|
|
934
|
+
}
|
|
935
|
+
return [
|
|
936
|
+
finding3(
|
|
937
|
+
at,
|
|
938
|
+
"WARN",
|
|
939
|
+
`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`
|
|
940
|
+
)
|
|
941
|
+
];
|
|
942
|
+
});
|
|
943
|
+
};
|
|
944
|
+
var OBSERVABILITY = "@geonosis/observability";
|
|
945
|
+
var COMPOSITION_ROOT = "src/platform.ts";
|
|
946
|
+
var FLOOR_PACKAGES = [
|
|
947
|
+
"@geonosis/conformance",
|
|
948
|
+
"@geonosis/db",
|
|
949
|
+
"@geonosis/events",
|
|
950
|
+
"@geonosis/search",
|
|
951
|
+
"@geonosis/workflows"
|
|
952
|
+
];
|
|
953
|
+
var COMPOSABLE = [
|
|
954
|
+
{
|
|
955
|
+
name: OBSERVABILITY,
|
|
956
|
+
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`
|
|
957
|
+
},
|
|
958
|
+
...FLOOR_PACKAGES.map((name) => ({
|
|
959
|
+
name,
|
|
960
|
+
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`
|
|
961
|
+
}))
|
|
962
|
+
];
|
|
963
|
+
var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
|
|
964
|
+
var composedHere = (root, workspaces) => {
|
|
965
|
+
const declared = declaredAnywhere(workspaces);
|
|
966
|
+
const wanted = COMPOSABLE.filter((one) => declared.has(one.name));
|
|
967
|
+
if (wanted.length === 0) return [];
|
|
968
|
+
const importing = /* @__PURE__ */ new Map();
|
|
969
|
+
for (const path of filesUnder(root, (name) => SOURCE_FILE.test(name))) {
|
|
970
|
+
let body;
|
|
971
|
+
try {
|
|
972
|
+
body = readFileSync5(path, "utf8");
|
|
973
|
+
} catch {
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
for (const one of wanted) {
|
|
977
|
+
if (!importing.has(one.name) && body.includes(one.name)) importing.set(one.name, path);
|
|
978
|
+
}
|
|
979
|
+
if (importing.size === wanted.length) break;
|
|
980
|
+
}
|
|
981
|
+
return wanted.map((one) => {
|
|
982
|
+
const at = importing.get(one.name);
|
|
983
|
+
return at === void 0 ? finding3(
|
|
984
|
+
one.name,
|
|
985
|
+
"WARN",
|
|
986
|
+
`it is declared here and no source file imports it \u2014 ${one.uncomposed}`
|
|
987
|
+
) : finding3(
|
|
988
|
+
one.name,
|
|
989
|
+
"OK",
|
|
990
|
+
`composed in ${relativePath(root, at)} \u2014 something here builds what it reports through`
|
|
991
|
+
);
|
|
992
|
+
});
|
|
993
|
+
};
|
|
792
994
|
var READERS = {
|
|
793
995
|
ledger: "@geonosis/ledger",
|
|
794
996
|
review: "@geonosis/review",
|
|
@@ -881,8 +1083,8 @@ var pluginDirsOf = (level) => {
|
|
|
881
1083
|
return { manifests: options.manifests ?? ["index.ts"], registry, roots: options.roots };
|
|
882
1084
|
};
|
|
883
1085
|
var pluginDirLayers = (root) => {
|
|
884
|
-
const path =
|
|
885
|
-
if (!
|
|
1086
|
+
const path = join5(root, ".oxlintrc.json");
|
|
1087
|
+
if (!existsSync4(path)) return [];
|
|
886
1088
|
const layers = [];
|
|
887
1089
|
for (const layer of layersOf(readConfig(path, root), PLUGIN_DIR_RULE)) {
|
|
888
1090
|
const dirs = pluginDirsOf(layer.level);
|
|
@@ -891,8 +1093,8 @@ var pluginDirLayers = (root) => {
|
|
|
891
1093
|
return layers;
|
|
892
1094
|
};
|
|
893
1095
|
var layerOver = (layers, root, relative) => {
|
|
894
|
-
const at =
|
|
895
|
-
const inside =
|
|
1096
|
+
const at = join5(root, relative);
|
|
1097
|
+
const inside = readdirSync3(at, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => `${relative}/${entry.name}`);
|
|
896
1098
|
const paths = inside.length === 0 ? [relative] : inside;
|
|
897
1099
|
const wrapped = layers.map((layer) => ({ files: layer.files, level: layer.dirs }));
|
|
898
1100
|
for (const path of paths) {
|
|
@@ -913,7 +1115,7 @@ var pluginDirs = (root) => {
|
|
|
913
1115
|
];
|
|
914
1116
|
}
|
|
915
1117
|
const missing = [...new Set(layers.flatMap((layer) => layer.dirs.registry))].filter(
|
|
916
|
-
(registry) => !
|
|
1118
|
+
(registry) => !existsSync4(join5(root, registry))
|
|
917
1119
|
);
|
|
918
1120
|
if (missing.length > 0) {
|
|
919
1121
|
return missing.map(
|
|
@@ -923,17 +1125,17 @@ var pluginDirs = (root) => {
|
|
|
923
1125
|
const source = /* @__PURE__ */ new Map();
|
|
924
1126
|
const unreachable = /* @__PURE__ */ new Map();
|
|
925
1127
|
for (const rootDir of new Set(layers.flatMap((layer) => layer.dirs.roots))) {
|
|
926
|
-
const at =
|
|
927
|
-
if (!
|
|
928
|
-
for (const entry of
|
|
1128
|
+
const at = join5(root, rootDir);
|
|
1129
|
+
if (!existsSync4(at)) continue;
|
|
1130
|
+
for (const entry of readdirSync3(at, { withFileTypes: true })) {
|
|
929
1131
|
if (!entry.isDirectory()) continue;
|
|
930
1132
|
const relative = `${rootDir}/${entry.name}`;
|
|
931
1133
|
const governs = layerOver(layers, root, relative);
|
|
932
1134
|
if (governs === void 0) continue;
|
|
933
1135
|
const key = governs.registry.join(", ");
|
|
934
|
-
const registry = source.get(key) ?? governs.registry.map((half) =>
|
|
1136
|
+
const registry = source.get(key) ?? governs.registry.map((half) => readFileSync5(join5(root, half), "utf8")).join("\n");
|
|
935
1137
|
source.set(key, registry);
|
|
936
|
-
const hasManifest = governs.manifests.some((name) =>
|
|
1138
|
+
const hasManifest = governs.manifests.some((name) => existsSync4(join5(at, entry.name, name)));
|
|
937
1139
|
if (!hasManifest || registry.includes(entry.name)) continue;
|
|
938
1140
|
unreachable.set(key, [...unreachable.get(key) ?? [], relative]);
|
|
939
1141
|
}
|
|
@@ -951,9 +1153,9 @@ var WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
|
951
1153
|
var HOIST_KEY = "publicHoistPattern";
|
|
952
1154
|
var HOIST_REPAIR = "rm -rf node_modules/.modules.yaml node_modules/.pnpm-workspace-state-v1.json && pnpm install";
|
|
953
1155
|
var hoistPatterns = (root) => {
|
|
954
|
-
const path =
|
|
955
|
-
if (!
|
|
956
|
-
const lines =
|
|
1156
|
+
const path = join5(root, WORKSPACE_YAML);
|
|
1157
|
+
if (!existsSync4(path)) return void 0;
|
|
1158
|
+
const lines = readFileSync5(path, "utf8").split("\n");
|
|
957
1159
|
const at = lines.findIndex((line) => new RegExp(`^${HOIST_KEY}\\s*:`).test(line));
|
|
958
1160
|
if (at < 0) return void 0;
|
|
959
1161
|
const patterns = [];
|
|
@@ -990,7 +1192,7 @@ var publicHoists = (root, workspaces) => {
|
|
|
990
1192
|
`this ${HOIST_KEY} matches no workspace package here, so what it should have linked at the root is a question this cannot answer`
|
|
991
1193
|
);
|
|
992
1194
|
}
|
|
993
|
-
const pruned = hoisted.filter((name) => !
|
|
1195
|
+
const pruned = hoisted.filter((name) => !existsSync4(join5(root, "node_modules", name)));
|
|
994
1196
|
if (pruned.length === 0) {
|
|
995
1197
|
return finding3(
|
|
996
1198
|
pattern,
|
|
@@ -1005,71 +1207,10 @@ var publicHoists = (root, workspaces) => {
|
|
|
1005
1207
|
);
|
|
1006
1208
|
});
|
|
1007
1209
|
};
|
|
1008
|
-
var HOOK_FILES = ["lefthook.yml", "lefthook.yaml", ".lefthook.yml", ".lefthook.yaml"];
|
|
1009
|
-
var HOOK_DIRS = [".husky", ".githooks"];
|
|
1010
|
-
var VERSION_MANAGED = /(?:^|[\s;&|(])(?<runner>pnpm|npx|bunx|yarn)\b(?:\s+(?:exec|run|dlx|x))?\s+(?<bin>geonosis(?:-[a-z-]+)?)\b/;
|
|
1011
|
-
var NAMES_A_BIN = /(?:^|[\s;&|(/])geonosis(?:-[a-z-]+)?\b/;
|
|
1012
|
-
var hookLines = (root) => {
|
|
1013
|
-
const found = [];
|
|
1014
|
-
const read = (path) => {
|
|
1015
|
-
const at = relativePath(root, path);
|
|
1016
|
-
for (const line of readFileSync4(path, "utf8").split("\n")) {
|
|
1017
|
-
if (line.trim() !== "") found.push({ at, line });
|
|
1018
|
-
}
|
|
1019
|
-
};
|
|
1020
|
-
for (const name of HOOK_FILES) {
|
|
1021
|
-
const path = join4(root, name);
|
|
1022
|
-
if (existsSync3(path)) read(path);
|
|
1023
|
-
}
|
|
1024
|
-
for (const name of HOOK_DIRS) {
|
|
1025
|
-
const dir = join4(root, name);
|
|
1026
|
-
if (!existsSync3(dir)) continue;
|
|
1027
|
-
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
1028
|
-
if (entry.isFile() && !entry.name.startsWith("_") && !entry.name.startsWith(".")) {
|
|
1029
|
-
read(join4(dir, entry.name));
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
return found;
|
|
1034
|
-
};
|
|
1035
|
-
var gitHooks = (root) => {
|
|
1036
|
-
const lines = hookLines(root);
|
|
1037
|
-
if (lines.length === 0) {
|
|
1038
|
-
return [finding3("git hooks", "SKIP", "no lefthook, husky or .githooks file here to read")];
|
|
1039
|
-
}
|
|
1040
|
-
const files = [...new Set(lines.map((one) => one.at))].toSorted();
|
|
1041
|
-
return files.flatMap((at) => {
|
|
1042
|
-
const here = lines.filter((one) => one.at === at);
|
|
1043
|
-
const named2 = here.filter((one) => NAMES_A_BIN.test(one.line));
|
|
1044
|
-
if (named2.length === 0) {
|
|
1045
|
-
return [finding3(at, "SKIP", "it names no geonosis bin, so there is nothing here to start")];
|
|
1046
|
-
}
|
|
1047
|
-
const wrong = named2.flatMap((one) => {
|
|
1048
|
-
const found = VERSION_MANAGED.exec(one.line)?.groups;
|
|
1049
|
-
return found === void 0 ? [] : [{ bin: found["bin"] ?? "", runner: found["runner"] ?? "" }];
|
|
1050
|
-
});
|
|
1051
|
-
if (wrong.length === 0) {
|
|
1052
|
-
return [
|
|
1053
|
-
finding3(
|
|
1054
|
-
at,
|
|
1055
|
-
"OK",
|
|
1056
|
-
`${named2.length} geonosis bin(s) here, every one called directly rather than through a runner`
|
|
1057
|
-
)
|
|
1058
|
-
];
|
|
1059
|
-
}
|
|
1060
|
-
return wrong.map(
|
|
1061
|
-
({ bin, runner }) => finding3(
|
|
1062
|
-
at,
|
|
1063
|
-
"FAIL",
|
|
1064
|
-
`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}`
|
|
1065
|
-
)
|
|
1066
|
-
);
|
|
1067
|
-
});
|
|
1068
|
-
};
|
|
1069
1210
|
var checkDrift = ({
|
|
1070
1211
|
readers = READERS,
|
|
1071
1212
|
root,
|
|
1072
|
-
userSettings =
|
|
1213
|
+
userSettings = join5(homedir(), SETTINGS),
|
|
1073
1214
|
workspaces
|
|
1074
1215
|
}) => {
|
|
1075
1216
|
const config = readGeonosis(root);
|
|
@@ -1082,7 +1223,9 @@ var checkDrift = ({
|
|
|
1082
1223
|
...generatedFiles(root, workspaces),
|
|
1083
1224
|
...pluginDirs(root),
|
|
1084
1225
|
...publicHoists(root, workspaces),
|
|
1085
|
-
...
|
|
1226
|
+
...composedHere(root, workspaces),
|
|
1227
|
+
...lintScopes(root, workspaces),
|
|
1228
|
+
...platformSplitBuilds(root),
|
|
1086
1229
|
...law(root, config),
|
|
1087
1230
|
...hooks(root, userSettings),
|
|
1088
1231
|
...blocks(root, config, readers, workspaces)
|
|
@@ -1091,9 +1234,17 @@ var checkDrift = ({
|
|
|
1091
1234
|
|
|
1092
1235
|
// src/exercised.ts
|
|
1093
1236
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1094
|
-
import {
|
|
1237
|
+
import {
|
|
1238
|
+
cpSync,
|
|
1239
|
+
existsSync as existsSync5,
|
|
1240
|
+
mkdirSync,
|
|
1241
|
+
mkdtempSync,
|
|
1242
|
+
readFileSync as readFileSync6,
|
|
1243
|
+
rmSync,
|
|
1244
|
+
writeFileSync
|
|
1245
|
+
} from "fs";
|
|
1095
1246
|
import { tmpdir } from "os";
|
|
1096
|
-
import { dirname as dirname2, join as
|
|
1247
|
+
import { dirname as dirname2, join as join6, resolve } from "path";
|
|
1097
1248
|
import { corpusOf, readManifest } from "@geonosis/lint-parity";
|
|
1098
1249
|
var OFF = /* @__PURE__ */ new Set([0, "0", "allow", "off", false]);
|
|
1099
1250
|
var severityOf = (level) => Array.isArray(level) ? level[0] : level;
|
|
@@ -1107,21 +1258,21 @@ var finding4 = (subject, verdict, message) => ({
|
|
|
1107
1258
|
});
|
|
1108
1259
|
var LIMIT = 800;
|
|
1109
1260
|
var refusal = (error) => {
|
|
1110
|
-
const
|
|
1111
|
-
return
|
|
1261
|
+
const said2 = String(error.message).trim();
|
|
1262
|
+
return said2.length > LIMIT ? `${said2.slice(0, LIMIT)}\u2026` : said2;
|
|
1112
1263
|
};
|
|
1113
1264
|
var reasonFrom = (config, oxlint) => {
|
|
1114
|
-
const dir = mkdtempSync(
|
|
1265
|
+
const dir = mkdtempSync(join6(tmpdir(), "geonosis-doctor-why-"));
|
|
1115
1266
|
try {
|
|
1116
|
-
const probe =
|
|
1267
|
+
const probe = join6(dir, "probe.tsx");
|
|
1117
1268
|
writeFileSync(probe, "export const probe = 1\n");
|
|
1118
1269
|
const run = spawnSync2(
|
|
1119
1270
|
oxlint,
|
|
1120
1271
|
["--no-ignore", "--disable-nested-config", "--config", config, probe],
|
|
1121
1272
|
{ encoding: "utf8", env: { ...process.env, NO_COLOR: "1" }, maxBuffer: 8 * 1024 * 1024 }
|
|
1122
1273
|
);
|
|
1123
|
-
const
|
|
1124
|
-
return /
|
|
1274
|
+
const said2 = `${run.stdout ?? ""}${run.stderr ?? ""}`;
|
|
1275
|
+
return /[A-Z]\w*:\s*([^]+?)(?=\s+at\s+\S+\s+\(|\n|$)/.exec(said2)?.[1];
|
|
1125
1276
|
} catch {
|
|
1126
1277
|
return void 0;
|
|
1127
1278
|
} finally {
|
|
@@ -1180,6 +1331,20 @@ var enabledHere = (config, plugin) => [
|
|
|
1180
1331
|
...config.overrides.flatMap((one) => enabledRulesOf(one.rules, plugin))
|
|
1181
1332
|
])
|
|
1182
1333
|
].toSorted();
|
|
1334
|
+
var concreteDirOf = (glob) => {
|
|
1335
|
+
const segments = glob.split("/");
|
|
1336
|
+
const out = [];
|
|
1337
|
+
for (const segment of segments.slice(0, -1)) {
|
|
1338
|
+
if (segment === "**") continue;
|
|
1339
|
+
if (segment === "*") {
|
|
1340
|
+
out.push("geonosis");
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1343
|
+
if (segment.includes("*") || segment.includes("{")) return void 0;
|
|
1344
|
+
out.push(segment);
|
|
1345
|
+
}
|
|
1346
|
+
return out.join("/");
|
|
1347
|
+
};
|
|
1183
1348
|
var throughProbes = ({
|
|
1184
1349
|
config,
|
|
1185
1350
|
corpus,
|
|
@@ -1189,8 +1354,8 @@ var throughProbes = ({
|
|
|
1189
1354
|
}) => {
|
|
1190
1355
|
const refused = /* @__PURE__ */ new Map();
|
|
1191
1356
|
const placed = /* @__PURE__ */ new Set();
|
|
1192
|
-
const dir = mkdtempSync(
|
|
1193
|
-
const here =
|
|
1357
|
+
const dir = mkdtempSync(join6(tmpdir(), "geonosis-doctor-probe-"));
|
|
1358
|
+
const here = join6(dir, "corpus");
|
|
1194
1359
|
try {
|
|
1195
1360
|
cpSync(corpus, here, { recursive: true });
|
|
1196
1361
|
for (const rule of silent) {
|
|
@@ -1204,18 +1369,34 @@ var throughProbes = ({
|
|
|
1204
1369
|
const files = write(layer.options);
|
|
1205
1370
|
if (files.length === 0) throw new Error("its probe declares no file at all");
|
|
1206
1371
|
const claims = layer.files.length === 0 || files.some((file) => layer.files.some((glob) => matchesGlob(glob, file.path)));
|
|
1372
|
+
let mounted = files;
|
|
1207
1373
|
if (!claims) {
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1374
|
+
const rehomed = layer.files.map((glob) => {
|
|
1375
|
+
const home = concreteDirOf(glob);
|
|
1376
|
+
if (home === void 0) return void 0;
|
|
1377
|
+
const extension = /^\*(\.[\w.]+)$/.exec(glob.split("/").at(-1) ?? "")?.[1];
|
|
1378
|
+
const moved = files.map((file) => {
|
|
1379
|
+
const base = file.path.split("/").at(-1) ?? file.path;
|
|
1380
|
+
const adapted = extension === void 0 || base.endsWith(extension) ? base : base.replace(/\.[^.]+$/, extension);
|
|
1381
|
+
const kept = layer.files.some((one) => matchesGlob(one, `${home}/${base}`));
|
|
1382
|
+
return { ...file, path: `${home}/${kept ? base : adapted}` };
|
|
1383
|
+
});
|
|
1384
|
+
return moved.every((file) => layer.files.some((one) => matchesGlob(one, file.path))) ? moved : void 0;
|
|
1385
|
+
}).find((moved) => moved !== void 0);
|
|
1386
|
+
if (rehomed === void 0) {
|
|
1387
|
+
unclaimed.push(
|
|
1388
|
+
`its probe lands at ${files[0]?.path ?? ""}, which the entry enabling it claims none of (and no claimed path could be synthesized): ${layer.files.join(", ")}`
|
|
1389
|
+
);
|
|
1390
|
+
continue;
|
|
1391
|
+
}
|
|
1392
|
+
mounted = rehomed;
|
|
1212
1393
|
}
|
|
1213
|
-
for (const file of
|
|
1214
|
-
const
|
|
1215
|
-
if (placed.has(
|
|
1216
|
-
placed.add(
|
|
1217
|
-
mkdirSync(dirname2(
|
|
1218
|
-
writeFileSync(
|
|
1394
|
+
for (const file of mounted) {
|
|
1395
|
+
const at2 = join6(here, file.path);
|
|
1396
|
+
if (placed.has(at2)) continue;
|
|
1397
|
+
placed.add(at2);
|
|
1398
|
+
mkdirSync(dirname2(at2), { recursive: true });
|
|
1399
|
+
writeFileSync(at2, file.source);
|
|
1219
1400
|
}
|
|
1220
1401
|
anyPlaced = true;
|
|
1221
1402
|
}
|
|
@@ -1224,17 +1405,33 @@ var throughProbes = ({
|
|
|
1224
1405
|
refused.set(rule, String(error.message));
|
|
1225
1406
|
}
|
|
1226
1407
|
}
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1408
|
+
const probeConfig = JSON.parse(readFileSync6(config.path, "utf8"));
|
|
1409
|
+
const configDir = dirname2(config.path);
|
|
1410
|
+
probeConfig.jsPlugins = (probeConfig.jsPlugins ?? []).map(
|
|
1411
|
+
(spec) => spec.startsWith(".") || spec.startsWith("/") ? resolve(configDir, spec) : packageDirOf(resolveFrom(configDir, spec), spec)
|
|
1412
|
+
);
|
|
1413
|
+
const at = join6(here, ".oxlintrc-geonosis-probe.json");
|
|
1414
|
+
writeFileSync(at, JSON.stringify(probeConfig, null, 2));
|
|
1415
|
+
const run = spawnSync2(
|
|
1416
|
+
oxlint,
|
|
1417
|
+
["-c", at, "--format", "json", "--no-ignore", "--disable-nested-config", here],
|
|
1418
|
+
{ cwd: dir, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }
|
|
1419
|
+
);
|
|
1420
|
+
const fired = /* @__PURE__ */ new Set();
|
|
1421
|
+
try {
|
|
1422
|
+
const parsed = JSON.parse(run.stdout || "{}");
|
|
1423
|
+
for (const one of parsed.diagnostics ?? []) {
|
|
1424
|
+
const renamed = /^([\w@./-]+)\((.+)\)$/.exec(one.code ?? "");
|
|
1425
|
+
if (renamed !== null) fired.add(`${renamed[1]}/${renamed[2]}`);
|
|
1426
|
+
}
|
|
1427
|
+
} catch {
|
|
1428
|
+
}
|
|
1429
|
+
return { fired, refused };
|
|
1234
1430
|
} finally {
|
|
1235
1431
|
rmSync(dir, { force: true, recursive: true });
|
|
1236
1432
|
}
|
|
1237
1433
|
};
|
|
1434
|
+
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";
|
|
1238
1435
|
var checkExercised = async ({
|
|
1239
1436
|
config,
|
|
1240
1437
|
corpus,
|
|
@@ -1243,15 +1440,15 @@ var checkExercised = async ({
|
|
|
1243
1440
|
repoCorpus,
|
|
1244
1441
|
root
|
|
1245
1442
|
}) => {
|
|
1246
|
-
const
|
|
1247
|
-
if (repoCorpus !== void 0 && !
|
|
1248
|
-
return
|
|
1443
|
+
const said2 = (verdict, message) => finding4(config.relative, verdict, message);
|
|
1444
|
+
if (repoCorpus !== void 0 && !existsSync5(repoCorpus)) {
|
|
1445
|
+
return said2(
|
|
1249
1446
|
"FAIL",
|
|
1250
1447
|
`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`
|
|
1251
1448
|
);
|
|
1252
1449
|
}
|
|
1253
|
-
if (!
|
|
1254
|
-
return
|
|
1450
|
+
if (!existsSync5(corpus)) {
|
|
1451
|
+
return said2(
|
|
1255
1452
|
"SKIP",
|
|
1256
1453
|
`the plugin loaded from here ships no corpus at ${relativeToRoot(root, corpus)} \u2014 nothing declares which rules it can be evidence about`
|
|
1257
1454
|
);
|
|
@@ -1260,15 +1457,15 @@ var checkExercised = async ({
|
|
|
1260
1457
|
try {
|
|
1261
1458
|
manifest = readManifest(corpus);
|
|
1262
1459
|
} catch (error) {
|
|
1263
|
-
return
|
|
1460
|
+
return said2("SKIP", refusal(error));
|
|
1264
1461
|
}
|
|
1265
1462
|
const enabled = enabledHere(config, manifest.plugin);
|
|
1266
|
-
if (enabled.length === 0) return
|
|
1463
|
+
if (enabled.length === 0) return said2("SKIP", `no ${manifest.plugin} rule is enabled here`);
|
|
1267
1464
|
let reach;
|
|
1268
1465
|
try {
|
|
1269
1466
|
reach = corpusOf({ configA: config.path, configB: config.path, corpus, oxlint }).reach;
|
|
1270
1467
|
} catch (error) {
|
|
1271
|
-
return
|
|
1468
|
+
return said2(
|
|
1272
1469
|
"FAIL",
|
|
1273
1470
|
`oxlint refused to run this config over the corpus, so nothing here fired at all \u2014 ${reasonFrom(config.path, oxlint) ?? refusal(error)}`
|
|
1274
1471
|
);
|
|
@@ -1276,7 +1473,7 @@ var checkExercised = async ({
|
|
|
1276
1473
|
const fired = new Set(reach.filter((one) => one.firedInA).map((one) => one.rule));
|
|
1277
1474
|
const unknown = enabled.filter((rule) => !manifest.rules.includes(rule));
|
|
1278
1475
|
if (unknown.length > 0) {
|
|
1279
|
-
return
|
|
1476
|
+
return said2(
|
|
1280
1477
|
"FAIL",
|
|
1281
1478
|
`${countOf(unknown, "name")} the loaded plugin does not export: ${named(unknown)}`
|
|
1282
1479
|
);
|
|
@@ -1286,12 +1483,12 @@ var checkExercised = async ({
|
|
|
1286
1483
|
try {
|
|
1287
1484
|
own = ownReach({ config, oxlint, repoCorpus });
|
|
1288
1485
|
} catch (error) {
|
|
1289
|
-
return
|
|
1486
|
+
return said2("FAIL", `this repo's own corpus could not be run \u2014 ${refusal(error)}`);
|
|
1290
1487
|
}
|
|
1291
1488
|
}
|
|
1292
1489
|
const empty = own.claimed.filter((rule) => !own.fired.has(rule));
|
|
1293
1490
|
if (empty.length > 0) {
|
|
1294
|
-
return
|
|
1491
|
+
return said2(
|
|
1295
1492
|
"FAIL",
|
|
1296
1493
|
`${countOf(empty, "fire")} nowhere in this repo's own corpus, which names them: ${named(empty)}`
|
|
1297
1494
|
);
|
|
@@ -1299,9 +1496,10 @@ var checkExercised = async ({
|
|
|
1299
1496
|
const silent = enabled.filter((rule) => !fired.has(rule) && !own.fired.has(rule));
|
|
1300
1497
|
const where = own.claimed.length === 0 ? "" : ` (${own.fired.size} by this repo's own corpus)`;
|
|
1301
1498
|
if (silent.length === 0) {
|
|
1302
|
-
return
|
|
1499
|
+
return said2("OK", `${enabled.length} enabled, ${enabled.length} exercised${where} \u2014 ${BASE}`);
|
|
1303
1500
|
}
|
|
1304
|
-
const
|
|
1501
|
+
const loadedProbes = entry === void 0 ? null : await probesOf(entry, manifest.plugin).catch(() => null);
|
|
1502
|
+
const probes = loadedProbes ?? {};
|
|
1305
1503
|
const declared = silent.filter((rule) => probes[rule] !== void 0);
|
|
1306
1504
|
const unprobed = silent.filter((rule) => probes[rule] === void 0);
|
|
1307
1505
|
let probed = { fired: /* @__PURE__ */ new Set(), refused: /* @__PURE__ */ new Map() };
|
|
@@ -1309,31 +1507,353 @@ var checkExercised = async ({
|
|
|
1309
1507
|
try {
|
|
1310
1508
|
probed = throughProbes({ config, corpus, oxlint, probes, silent: declared });
|
|
1311
1509
|
} catch (error) {
|
|
1312
|
-
return
|
|
1510
|
+
return said2("FAIL", `the declared probes could not be run \u2014 ${refusal(error)}`);
|
|
1313
1511
|
}
|
|
1314
1512
|
}
|
|
1315
1513
|
const exercised = declared.filter((rule) => probed.fired.has(rule));
|
|
1316
1514
|
const inert = declared.filter((rule) => !probed.fired.has(rule));
|
|
1317
1515
|
const placed = inert.filter((rule) => !probed.refused.has(rule));
|
|
1318
1516
|
if (placed.length > 0) {
|
|
1319
|
-
return
|
|
1517
|
+
return said2(
|
|
1320
1518
|
"FAIL",
|
|
1321
1519
|
`${countOf(placed, "fire")} nowhere in the corpus and nothing through the probe each declares either: ${named(placed.map((rule) => under(config, rule)))}`
|
|
1322
1520
|
);
|
|
1323
1521
|
}
|
|
1324
|
-
const unplaceable =
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
),
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1522
|
+
const unplaceable = inert.map((rule) => `${rule}: ${probed.refused.get(rule) ?? ""}`);
|
|
1523
|
+
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(", ")}`;
|
|
1524
|
+
const unknowable = loadedProbes === null ? unprobed.map(
|
|
1525
|
+
(rule) => `${rule} declares no probe, so ${optionSetsOf(config, rule).every((options) => options.length === 0) ? "its scope" : under(config, rule)} does not reach the corpus`
|
|
1526
|
+
) : [];
|
|
1527
|
+
if (unplaceable.length > 0 || unknowable.length > 0) {
|
|
1528
|
+
return said2(
|
|
1529
|
+
"UNJUDGED",
|
|
1530
|
+
named([...unknowable, ...unplaceable]) + (kitNote === "" ? "" : ` \u2014 and ${kitNote}`)
|
|
1531
|
+
);
|
|
1532
|
+
}
|
|
1533
|
+
if (kitNote !== "") return said2("WARN", kitNote);
|
|
1331
1534
|
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`;
|
|
1332
|
-
return
|
|
1535
|
+
return said2(
|
|
1536
|
+
"OK",
|
|
1537
|
+
`${enabled.length} enabled, ${enabled.length} exercised${where} \u2014 ${through}. ${BASE}`
|
|
1538
|
+
);
|
|
1539
|
+
};
|
|
1540
|
+
|
|
1541
|
+
// src/envelope.ts
|
|
1542
|
+
import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync7 } from "fs";
|
|
1543
|
+
import { join as join7 } from "path";
|
|
1544
|
+
var ENVELOPES_DIR = ".geonosis/envelopes";
|
|
1545
|
+
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";
|
|
1546
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1547
|
+
var finding5 = (verdict, subject, message) => ({
|
|
1548
|
+
check: "envelope",
|
|
1549
|
+
message,
|
|
1550
|
+
subject,
|
|
1551
|
+
verdict
|
|
1552
|
+
});
|
|
1553
|
+
var lengthOf = (value) => Array.isArray(value) ? value.length : void 0;
|
|
1554
|
+
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";
|
|
1555
|
+
var judge = (subject, name, parsed) => {
|
|
1556
|
+
if (!isRecord2(parsed)) return finding5("FAIL", subject, `is not an object. ${NEXT}`);
|
|
1557
|
+
const tool = parsed["tool"];
|
|
1558
|
+
if (typeof tool !== "string" || tool.trim() === "") {
|
|
1559
|
+
return finding5(
|
|
1560
|
+
"FAIL",
|
|
1561
|
+
subject,
|
|
1562
|
+
`names no tool, so nothing here says which run it is about. ${NEXT}`
|
|
1563
|
+
);
|
|
1564
|
+
}
|
|
1565
|
+
if (tool !== name) {
|
|
1566
|
+
return finding5(
|
|
1567
|
+
"FAIL",
|
|
1568
|
+
subject,
|
|
1569
|
+
`is written by "${tool}", not "${name}" \u2014 a tool writing another tool's envelope leaves both of their numbers unattributable. ${NEXT}`
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
const version = parsed["version"];
|
|
1573
|
+
if (typeof version !== "string" || version.trim() === "") {
|
|
1574
|
+
return finding5(
|
|
1575
|
+
"FAIL",
|
|
1576
|
+
subject,
|
|
1577
|
+
`${tool}: names no version, so nothing dates this run and a stale envelope reads exactly like a fresh one. ${NEXT}`
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
const considered = parsed["considered"];
|
|
1581
|
+
const read = parsed["read"];
|
|
1582
|
+
const refused = lengthOf(parsed["refused"]);
|
|
1583
|
+
const excused = lengthOf(parsed["excused"]);
|
|
1584
|
+
if (typeof considered !== "number" || typeof read !== "number" || refused === void 0 || excused === void 0) {
|
|
1585
|
+
return finding5(
|
|
1586
|
+
"FAIL",
|
|
1587
|
+
subject,
|
|
1588
|
+
`${tool}: has no considered/read/refused/excused to check \u2014 the four numbers ARE the envelope, and a file without them measures nothing. ${NEXT}`
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1591
|
+
const accounted = read + refused + excused;
|
|
1592
|
+
return considered === accounted ? finding5(
|
|
1593
|
+
"OK",
|
|
1594
|
+
subject,
|
|
1595
|
+
`${tool} considered ${considered} and accounts for all of them \u2014 ${read} read + ${refused} refused + ${excused} excused`
|
|
1596
|
+
) : finding5(
|
|
1597
|
+
"FAIL",
|
|
1598
|
+
subject,
|
|
1599
|
+
`${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}`
|
|
1600
|
+
);
|
|
1601
|
+
};
|
|
1602
|
+
var checkEnvelopes = ({ root }) => {
|
|
1603
|
+
const dir = join7(root, ENVELOPES_DIR);
|
|
1604
|
+
const files = existsSync6(dir) ? readdirSync4(dir).filter((name) => name.endsWith(".json")).toSorted() : [];
|
|
1605
|
+
if (files.length === 0) return [finding5("SKIP", ENVELOPES_DIR, NO_ENVELOPES)];
|
|
1606
|
+
return files.map((name) => {
|
|
1607
|
+
const subject = `${ENVELOPES_DIR}/${name}`;
|
|
1608
|
+
let parsed;
|
|
1609
|
+
try {
|
|
1610
|
+
parsed = JSON.parse(readFileSync7(join7(dir, name), "utf8"));
|
|
1611
|
+
} catch (error) {
|
|
1612
|
+
return finding5("FAIL", subject, `is not readable JSON: ${error.message}. ${NEXT}`);
|
|
1613
|
+
}
|
|
1614
|
+
return judge(subject, name.replace(/\.json$/, ""), parsed);
|
|
1615
|
+
});
|
|
1616
|
+
};
|
|
1617
|
+
|
|
1618
|
+
// src/repo-corpus.ts
|
|
1619
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
1620
|
+
import { join as join8 } from "path";
|
|
1621
|
+
var GEONOSIS_FILE = "geonosis.json";
|
|
1622
|
+
var repoCorpusOf = (root) => {
|
|
1623
|
+
const path = join8(root, GEONOSIS_FILE);
|
|
1624
|
+
if (!existsSync7(path)) return void 0;
|
|
1625
|
+
try {
|
|
1626
|
+
const parsed = JSON.parse(readFileSync8(path, "utf8"));
|
|
1627
|
+
const declared = parsed.doctor?.corpus;
|
|
1628
|
+
return typeof declared === "string" && declared !== "" ? join8(root, declared) : void 0;
|
|
1629
|
+
} catch {
|
|
1630
|
+
return void 0;
|
|
1631
|
+
}
|
|
1632
|
+
};
|
|
1633
|
+
|
|
1634
|
+
// src/group.ts
|
|
1635
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
|
|
1636
|
+
import { join as join9 } from "path";
|
|
1637
|
+
var FIXED_GROUP = [
|
|
1638
|
+
"@geonosis/cli",
|
|
1639
|
+
"@geonosis/doctor",
|
|
1640
|
+
"@geonosis/evals",
|
|
1641
|
+
"@geonosis/integrations",
|
|
1642
|
+
"@geonosis/ledger",
|
|
1643
|
+
"@geonosis/lint-parity",
|
|
1644
|
+
"@geonosis/mcp",
|
|
1645
|
+
"@geonosis/observability",
|
|
1646
|
+
"@geonosis/oxlint-plugin-biological-architecture",
|
|
1647
|
+
"@geonosis/policy",
|
|
1648
|
+
"@geonosis/rails",
|
|
1649
|
+
"@geonosis/ratchet",
|
|
1650
|
+
"@geonosis/release",
|
|
1651
|
+
"@geonosis/review",
|
|
1652
|
+
"@geonosis/testbed",
|
|
1653
|
+
"@geonosis/themekit",
|
|
1654
|
+
"@geonosis/verify",
|
|
1655
|
+
"@geonosis/verify-arch",
|
|
1656
|
+
"@geonosis/visual-diff",
|
|
1657
|
+
"@geonosis/walk",
|
|
1658
|
+
"create-geonosis",
|
|
1659
|
+
"geonosis"
|
|
1660
|
+
];
|
|
1661
|
+
var KIT_GROUP = { name: "@geonosis fixed group", packages: FIXED_GROUP };
|
|
1662
|
+
var declaredGroupsOf = (root) => {
|
|
1663
|
+
const path = join9(root, GEONOSIS_FILE);
|
|
1664
|
+
if (!existsSync8(path)) return void 0;
|
|
1665
|
+
let parsed;
|
|
1666
|
+
try {
|
|
1667
|
+
parsed = JSON.parse(readFileSync9(path, "utf8"));
|
|
1668
|
+
} catch {
|
|
1669
|
+
return void 0;
|
|
1670
|
+
}
|
|
1671
|
+
const declared = parsed.doctor?.groups;
|
|
1672
|
+
if (declared === void 0) return void 0;
|
|
1673
|
+
if (!Array.isArray(declared) || declared.length === 0) {
|
|
1674
|
+
throw new DoctorError(
|
|
1675
|
+
`${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`
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
return declared.map((one, at) => {
|
|
1679
|
+
const group = one;
|
|
1680
|
+
if (typeof group.name !== "string" || !Array.isArray(group.packages) || group.packages.length < 2) {
|
|
1681
|
+
throw new DoctorError(
|
|
1682
|
+
`${GEONOSIS_FILE} doctor.groups[${at}] needs a name and at least two packages \u2014 one package cannot disagree with itself`
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1685
|
+
return { name: group.name, packages: group.packages };
|
|
1686
|
+
});
|
|
1687
|
+
};
|
|
1688
|
+
var labelOf = (workspace) => workspace.relative === "" ? "root" : workspace.relative;
|
|
1689
|
+
var versionOf = (dir, name) => {
|
|
1690
|
+
let entry;
|
|
1691
|
+
try {
|
|
1692
|
+
entry = resolveFrom(dir, name);
|
|
1693
|
+
} catch {
|
|
1694
|
+
return { kind: "absent" };
|
|
1695
|
+
}
|
|
1696
|
+
try {
|
|
1697
|
+
const manifest = JSON.parse(
|
|
1698
|
+
readFileSync9(join9(packageDirOf(entry, name), "package.json"), "utf8")
|
|
1699
|
+
);
|
|
1700
|
+
return typeof manifest.version === "string" ? { kind: "read", version: manifest.version } : { kind: "unreadable", why: "its package.json declares no version" };
|
|
1701
|
+
} catch (error) {
|
|
1702
|
+
return { kind: "unreadable", why: String(error.message).split("\n")[0] ?? "" };
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
var finding6 = (subject, verdict, message) => ({
|
|
1706
|
+
check: "group",
|
|
1707
|
+
message,
|
|
1708
|
+
subject,
|
|
1709
|
+
verdict
|
|
1710
|
+
});
|
|
1711
|
+
var said = (versions) => [...versions.entries()].map(([version, from]) => `${version} (${from.join(", ")})`).join("; ");
|
|
1712
|
+
var repoWide = (name, found) => {
|
|
1713
|
+
const versions = /* @__PURE__ */ new Map();
|
|
1714
|
+
for (const one of found) versions.set(one.version, [...versions.get(one.version) ?? [], one.at]);
|
|
1715
|
+
if (versions.size === 1) return void 0;
|
|
1716
|
+
return finding6(
|
|
1717
|
+
name,
|
|
1718
|
+
"FAIL",
|
|
1719
|
+
`${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`
|
|
1720
|
+
);
|
|
1721
|
+
};
|
|
1722
|
+
var perWorkspace = (group, at, found) => {
|
|
1723
|
+
if (new Set(found.map((one) => one.version)).size === 1) return void 0;
|
|
1724
|
+
const listed = found.map((one) => `${one.name} ${one.version}`).join(", ");
|
|
1725
|
+
return finding6(
|
|
1726
|
+
at,
|
|
1727
|
+
"FAIL",
|
|
1728
|
+
`${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`
|
|
1729
|
+
);
|
|
1730
|
+
};
|
|
1731
|
+
var DOORS = ["@geonosis/cli", "geonosis"];
|
|
1732
|
+
var manifestOf = (dir, name) => {
|
|
1733
|
+
try {
|
|
1734
|
+
return JSON.parse(
|
|
1735
|
+
readFileSync9(join9(packageDirOf(resolveFrom(dir, name), name), "package.json"), "utf8")
|
|
1736
|
+
);
|
|
1737
|
+
} catch {
|
|
1738
|
+
return void 0;
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1741
|
+
var binNamesOf = (manifest, name) => {
|
|
1742
|
+
const declared = manifest?.bin;
|
|
1743
|
+
return typeof declared === "string" ? [name.replace(/^@[^/]+\//, "")] : Object.keys(declared ?? {});
|
|
1744
|
+
};
|
|
1745
|
+
var declaredIn2 = (manifest) => [
|
|
1746
|
+
...new Set(
|
|
1747
|
+
[
|
|
1748
|
+
manifest.dependencies,
|
|
1749
|
+
manifest.devDependencies,
|
|
1750
|
+
manifest.optionalDependencies,
|
|
1751
|
+
manifest.peerDependencies
|
|
1752
|
+
].flatMap((block) => Object.keys(block ?? {}))
|
|
1753
|
+
)
|
|
1754
|
+
];
|
|
1755
|
+
var rootDeclarations = (root, workspaces) => {
|
|
1756
|
+
const here = workspaces.find((one) => one.relative === "");
|
|
1757
|
+
if (here === void 0) return [];
|
|
1758
|
+
const declared = declaredIn2(here.manifest);
|
|
1759
|
+
const door = DOORS.find((name) => declared.includes(name));
|
|
1760
|
+
if (door === void 0) return [];
|
|
1761
|
+
const owned = new Set(
|
|
1762
|
+
[door, ...DOORS].flatMap((name) => {
|
|
1763
|
+
const manifest = manifestOf(root, name);
|
|
1764
|
+
return manifest === void 0 ? [] : Object.keys(manifest.dependencies ?? {});
|
|
1765
|
+
})
|
|
1766
|
+
);
|
|
1767
|
+
const callers = [
|
|
1768
|
+
...Object.values(here.manifest.scripts ?? {}),
|
|
1769
|
+
...hookLines(root).map((one) => one.line)
|
|
1770
|
+
].join("\n");
|
|
1771
|
+
const spare = declared.filter(
|
|
1772
|
+
(name) => !DOORS.includes(name) && owned.has(name) && !binNamesOf(manifestOf(root, name), name).some(
|
|
1773
|
+
(bin) => new RegExp(`(?:^|[\\s;&|(/])${bin}(?:\\s|$)`).test(callers)
|
|
1774
|
+
)
|
|
1775
|
+
);
|
|
1776
|
+
if (spare.length === 0) {
|
|
1777
|
+
return [
|
|
1778
|
+
finding6(
|
|
1779
|
+
"package.json",
|
|
1780
|
+
"OK",
|
|
1781
|
+
`every @geonosis package this root declares is ${door} or a bin its own scripts call by name`
|
|
1782
|
+
)
|
|
1783
|
+
];
|
|
1784
|
+
}
|
|
1785
|
+
return spare.map(
|
|
1786
|
+
(name) => finding6(
|
|
1787
|
+
name,
|
|
1788
|
+
"WARN",
|
|
1789
|
+
`the root declares it and no root script 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`
|
|
1790
|
+
)
|
|
1791
|
+
);
|
|
1792
|
+
};
|
|
1793
|
+
var checkGroup = ({
|
|
1794
|
+
groups,
|
|
1795
|
+
root,
|
|
1796
|
+
workspaces
|
|
1797
|
+
}) => {
|
|
1798
|
+
const findings = [...rootDeclarations(root, workspaces)];
|
|
1799
|
+
for (const group of groups ?? [KIT_GROUP]) {
|
|
1800
|
+
const found = [];
|
|
1801
|
+
for (const workspace of workspaces) {
|
|
1802
|
+
const at = labelOf(workspace);
|
|
1803
|
+
const here = [];
|
|
1804
|
+
for (const name of group.packages) {
|
|
1805
|
+
const reading = versionOf(workspace.dir, name);
|
|
1806
|
+
if (reading.kind === "read") here.push({ at, name, version: reading.version });
|
|
1807
|
+
if (reading.kind === "unreadable") {
|
|
1808
|
+
findings.push(
|
|
1809
|
+
finding6(
|
|
1810
|
+
`${at} \u2192 ${name}`,
|
|
1811
|
+
"UNJUDGED",
|
|
1812
|
+
`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`
|
|
1813
|
+
)
|
|
1814
|
+
);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
const split2 = here.length > 0 ? perWorkspace(group, at, here) : void 0;
|
|
1818
|
+
if (split2 !== void 0) findings.push(split2);
|
|
1819
|
+
found.push(...here);
|
|
1820
|
+
}
|
|
1821
|
+
if (found.length === 0) {
|
|
1822
|
+
findings.push(
|
|
1823
|
+
finding6(
|
|
1824
|
+
group.name,
|
|
1825
|
+
"SKIP",
|
|
1826
|
+
`no workspace under ${root} resolves any of ${group.packages.join(", ")}, so nothing here versions together`
|
|
1827
|
+
)
|
|
1828
|
+
);
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
const names = [...new Set(found.map((one) => one.name))].toSorted();
|
|
1832
|
+
const split = names.flatMap((name) => {
|
|
1833
|
+
const line = repoWide(
|
|
1834
|
+
name,
|
|
1835
|
+
found.filter((one) => one.name === name)
|
|
1836
|
+
);
|
|
1837
|
+
return line === void 0 ? [] : [line];
|
|
1838
|
+
});
|
|
1839
|
+
findings.push(...split);
|
|
1840
|
+
if (split.length === 0) {
|
|
1841
|
+
const versions = [...new Set(found.map((one) => one.version))];
|
|
1842
|
+
findings.push(
|
|
1843
|
+
finding6(
|
|
1844
|
+
group.name,
|
|
1845
|
+
"OK",
|
|
1846
|
+
`${names.length} of ${group.packages.length} installed, at ${versions.join(", ")}, one copy each across ${new Set(found.map((one) => one.at)).size} workspaces`
|
|
1847
|
+
)
|
|
1848
|
+
);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
return findings;
|
|
1333
1852
|
};
|
|
1334
1853
|
|
|
1335
1854
|
// src/loaded.ts
|
|
1336
|
-
import {
|
|
1855
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
1856
|
+
import { join as join10, sep as sep3 } from "path";
|
|
1337
1857
|
var SCOPE = "@geonosis/";
|
|
1338
1858
|
var BLOCKS = [
|
|
1339
1859
|
"dependencies",
|
|
@@ -1346,11 +1866,11 @@ var PINNED = /^[=v]?(\d+\.\d+\.\d+(?:[-+][\w.-]+)?)$/;
|
|
|
1346
1866
|
var RANGE = /^(\^|~|>=)\s*v?(\d+)\.(\d+)\.(\d+)$/;
|
|
1347
1867
|
var LINKED = /^(?:file|link|portal|workspace):/;
|
|
1348
1868
|
var ANY = /* @__PURE__ */ new Set(["", "*", "latest", "x"]);
|
|
1349
|
-
var
|
|
1869
|
+
var versionOf2 = (found, at) => Number(found[at] ?? Number.NaN);
|
|
1350
1870
|
var parts = (found) => [
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1871
|
+
versionOf2(found, 1),
|
|
1872
|
+
versionOf2(found, 2),
|
|
1873
|
+
versionOf2(found, 3)
|
|
1354
1874
|
];
|
|
1355
1875
|
var below = (here, bound) => {
|
|
1356
1876
|
for (const [at, one] of here.entries()) {
|
|
@@ -1371,9 +1891,9 @@ var satisfies = (version, spec) => {
|
|
|
1371
1891
|
if (here === null) return void 0;
|
|
1372
1892
|
const at = parts(here);
|
|
1373
1893
|
const bound = [
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1894
|
+
versionOf2(range, 2),
|
|
1895
|
+
versionOf2(range, 3),
|
|
1896
|
+
versionOf2(range, 4)
|
|
1377
1897
|
];
|
|
1378
1898
|
if (below(at, bound)) return false;
|
|
1379
1899
|
if (range[1] === ">=") return true;
|
|
@@ -1403,7 +1923,7 @@ var declaredFor = ({
|
|
|
1403
1923
|
}
|
|
1404
1924
|
return void 0;
|
|
1405
1925
|
};
|
|
1406
|
-
var
|
|
1926
|
+
var finding7 = (subject, verdict, message) => ({
|
|
1407
1927
|
check: "loaded",
|
|
1408
1928
|
message,
|
|
1409
1929
|
subject,
|
|
@@ -1420,36 +1940,36 @@ var oneConfig = async ({
|
|
|
1420
1940
|
specifier,
|
|
1421
1941
|
workspaces
|
|
1422
1942
|
}) => {
|
|
1423
|
-
const
|
|
1943
|
+
const said2 = (verdict, message) => finding7(config.relative, verdict, `${specifier}: ${message}`);
|
|
1424
1944
|
let loaded;
|
|
1425
1945
|
try {
|
|
1426
1946
|
loaded = await versionAt(resolveFrom(config.dir, specifier), specifier, root);
|
|
1427
1947
|
} catch (error) {
|
|
1428
|
-
return
|
|
1948
|
+
return said2(
|
|
1429
1949
|
"FAIL",
|
|
1430
1950
|
`could not resolve it from this config's directory, and oxlint resolves it from exactly there \u2014 ${firstLine(error)}`
|
|
1431
1951
|
);
|
|
1432
1952
|
}
|
|
1433
1953
|
const declared = declaredFor({ dir: config.dir, specifier, workspaces });
|
|
1434
1954
|
if (declared === void 0) {
|
|
1435
|
-
return
|
|
1955
|
+
return said2(
|
|
1436
1956
|
"FAIL",
|
|
1437
1957
|
`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`
|
|
1438
1958
|
);
|
|
1439
1959
|
}
|
|
1440
1960
|
const held = satisfies(loaded.version, declared.spec);
|
|
1441
1961
|
if (held === void 0) {
|
|
1442
|
-
return
|
|
1962
|
+
return said2(
|
|
1443
1963
|
"FAIL",
|
|
1444
1964
|
`loaded ${loaded.version}, and ${declared.at} declares "${declared.spec}" \u2014 a range this check cannot read, so it will not call the version right`
|
|
1445
1965
|
);
|
|
1446
1966
|
}
|
|
1447
|
-
return held ?
|
|
1967
|
+
return held ? said2("OK", `loaded ${loaded.version} = declared ${declared.spec} (${declared.at})`) : said2(
|
|
1448
1968
|
"FAIL",
|
|
1449
1969
|
`loaded ${loaded.version}, declared ${declared.spec} (${declared.at}) \u2014 a nested copy at ${loaded.at}. Remove the nested copies (rm -rf <workspace>/node_modules/@geonosis) and reinstall; the linter is not running what the tree declares until then`
|
|
1450
1970
|
);
|
|
1451
1971
|
};
|
|
1452
|
-
var
|
|
1972
|
+
var labelOf2 = (workspace) => workspace.relative === "" ? "root" : workspace.relative;
|
|
1453
1973
|
var copiesOf = async ({
|
|
1454
1974
|
root,
|
|
1455
1975
|
specifier,
|
|
@@ -1466,31 +1986,81 @@ var copiesOf = async ({
|
|
|
1466
1986
|
const at = relativeToRoot(root, packageDirOf(entry, specifier));
|
|
1467
1987
|
const already = found.get(at);
|
|
1468
1988
|
if (already !== void 0) {
|
|
1469
|
-
already.from.push(
|
|
1989
|
+
already.from.push(labelOf2(workspace));
|
|
1470
1990
|
continue;
|
|
1471
1991
|
}
|
|
1472
|
-
found.set(at, { from: [
|
|
1992
|
+
found.set(at, { from: [labelOf2(workspace)], version: await pluginVersionOf(entry) });
|
|
1473
1993
|
}
|
|
1474
1994
|
if (found.size === 0) {
|
|
1475
|
-
return
|
|
1995
|
+
return finding7(specifier, "FAIL", "no workspace in this tree can resolve it at all");
|
|
1476
1996
|
}
|
|
1477
1997
|
const listed = [...found.entries()].map(([at, one]) => `${at} ${one.version} (${one.from.join(", ")})`).join("; ");
|
|
1478
|
-
return found.size === 1 ?
|
|
1998
|
+
return found.size === 1 ? finding7(specifier, "OK", `1 copy \u2014 ${listed}`) : finding7(
|
|
1479
1999
|
specifier,
|
|
1480
2000
|
"WARN",
|
|
1481
2001
|
`${found.size} copies \u2014 ${listed}. Which one oxlint runs depends on which directory its config sits in.`
|
|
1482
2002
|
);
|
|
1483
2003
|
};
|
|
2004
|
+
var shipperOf = (root, bin) => {
|
|
2005
|
+
const name = bin === "geonosis" ? "@geonosis/cli" : `@geonosis/${bin.replace(/^geonosis-/, "")}`;
|
|
2006
|
+
try {
|
|
2007
|
+
const manifest = JSON.parse(
|
|
2008
|
+
readFileSync10(join10(packageDirOf(resolveFrom(root, name), name), "package.json"), "utf8")
|
|
2009
|
+
);
|
|
2010
|
+
const declared = manifest.bin;
|
|
2011
|
+
return typeof declared === "object" && !Object.hasOwn(declared, bin) ? "" : name;
|
|
2012
|
+
} catch {
|
|
2013
|
+
return name;
|
|
2014
|
+
}
|
|
2015
|
+
};
|
|
2016
|
+
var knipNote = (root, bin) => {
|
|
2017
|
+
const shipper = shipperOf(root, bin);
|
|
2018
|
+
return shipper === "" ? "" : `. knip resolves no dependency out of that path, so add ignoreDependencies: ["${shipper}"] to its config in the same commit`;
|
|
2019
|
+
};
|
|
2020
|
+
var gitHooks = (root) => {
|
|
2021
|
+
const lines = hookLines(root);
|
|
2022
|
+
if (lines.length === 0) {
|
|
2023
|
+
return [finding7("git hooks", "SKIP", "no lefthook, husky or .githooks file here to read")];
|
|
2024
|
+
}
|
|
2025
|
+
const files = [...new Set(lines.map((one) => one.at))].toSorted();
|
|
2026
|
+
return files.flatMap((at) => {
|
|
2027
|
+
const here = lines.filter((one) => one.at === at);
|
|
2028
|
+
const named2 = here.filter((one) => NAMES_A_BIN.test(one.line));
|
|
2029
|
+
if (named2.length === 0) {
|
|
2030
|
+
return [finding7(at, "SKIP", "it names no geonosis bin, so there is nothing here to start")];
|
|
2031
|
+
}
|
|
2032
|
+
const wrong = named2.flatMap((one) => {
|
|
2033
|
+
const found = VERSION_MANAGED.exec(one.line)?.groups;
|
|
2034
|
+
return found === void 0 ? [] : [{ bin: found["bin"] ?? "", runner: found["runner"] ?? "" }];
|
|
2035
|
+
});
|
|
2036
|
+
if (wrong.length === 0) {
|
|
2037
|
+
return [
|
|
2038
|
+
finding7(
|
|
2039
|
+
at,
|
|
2040
|
+
"OK",
|
|
2041
|
+
`${named2.length} geonosis bin(s) here, every one called directly rather than through a runner`
|
|
2042
|
+
)
|
|
2043
|
+
];
|
|
2044
|
+
}
|
|
2045
|
+
return wrong.map(
|
|
2046
|
+
({ bin, runner }) => finding7(
|
|
2047
|
+
at,
|
|
2048
|
+
"FAIL",
|
|
2049
|
+
`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)}`
|
|
2050
|
+
)
|
|
2051
|
+
);
|
|
2052
|
+
});
|
|
2053
|
+
};
|
|
1484
2054
|
var checkLoaded = async ({
|
|
1485
2055
|
configs,
|
|
1486
2056
|
root,
|
|
1487
2057
|
workspaces
|
|
1488
2058
|
}) => {
|
|
1489
|
-
const findings = [];
|
|
2059
|
+
const findings = [...gitHooks(root)];
|
|
1490
2060
|
const specifiers = /* @__PURE__ */ new Set();
|
|
1491
2061
|
for (const config of configs) {
|
|
1492
2062
|
if (config.error !== void 0) {
|
|
1493
|
-
findings.push(
|
|
2063
|
+
findings.push(finding7(config.relative, "FAIL", config.error));
|
|
1494
2064
|
continue;
|
|
1495
2065
|
}
|
|
1496
2066
|
for (const specifier of config.jsPlugins.filter((name) => name.startsWith(SCOPE))) {
|
|
@@ -1505,13 +2075,13 @@ var checkLoaded = async ({
|
|
|
1505
2075
|
};
|
|
1506
2076
|
|
|
1507
2077
|
// src/observability.ts
|
|
1508
|
-
import { readFileSync as
|
|
1509
|
-
import { join as
|
|
1510
|
-
var
|
|
2078
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
2079
|
+
import { join as join11 } from "path";
|
|
2080
|
+
var GEONOSIS_FILE2 = "geonosis.json";
|
|
1511
2081
|
var REACHES_NOTHING = /* @__PURE__ */ new Set(["console", "memory", "noop", "none", "null", "swallowing"]);
|
|
1512
2082
|
var DEFAULT_MAX_AGE_SECONDS = 3600;
|
|
1513
2083
|
var HEAD_TIMEOUT_MS = 3e3;
|
|
1514
|
-
var
|
|
2084
|
+
var finding8 = (verdict, subject, message) => ({
|
|
1515
2085
|
check: "observability",
|
|
1516
2086
|
message,
|
|
1517
2087
|
subject,
|
|
@@ -1520,7 +2090,7 @@ var finding6 = (verdict, subject, message) => ({
|
|
|
1520
2090
|
var readGeonosis2 = (root) => {
|
|
1521
2091
|
let text;
|
|
1522
2092
|
try {
|
|
1523
|
-
text =
|
|
2093
|
+
text = readFileSync11(join11(root, GEONOSIS_FILE2), "utf8");
|
|
1524
2094
|
} catch {
|
|
1525
2095
|
return { present: false };
|
|
1526
2096
|
}
|
|
@@ -1538,27 +2108,27 @@ var readGeonosis2 = (root) => {
|
|
|
1538
2108
|
var exporterFinding = (config) => {
|
|
1539
2109
|
const sink = config.sink;
|
|
1540
2110
|
if (typeof sink !== "string" || sink.trim() === "") {
|
|
1541
|
-
return
|
|
2111
|
+
return finding8(
|
|
1542
2112
|
"FAIL",
|
|
1543
|
-
|
|
2113
|
+
GEONOSIS_FILE2,
|
|
1544
2114
|
"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"
|
|
1545
2115
|
);
|
|
1546
2116
|
}
|
|
1547
2117
|
if (REACHES_NOTHING.has(sink.toLowerCase())) {
|
|
1548
|
-
return
|
|
2118
|
+
return finding8(
|
|
1549
2119
|
"WARN",
|
|
1550
|
-
|
|
2120
|
+
GEONOSIS_FILE2,
|
|
1551
2121
|
`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.`
|
|
1552
2122
|
);
|
|
1553
2123
|
}
|
|
1554
|
-
return
|
|
2124
|
+
return finding8("OK", GEONOSIS_FILE2, `the configured sink is "${sink}"`);
|
|
1555
2125
|
};
|
|
1556
2126
|
var reachableFinding = async (config) => {
|
|
1557
2127
|
const endpoint = config.endpoint;
|
|
1558
2128
|
if (typeof endpoint !== "string" || endpoint.trim() === "") {
|
|
1559
|
-
return
|
|
2129
|
+
return finding8(
|
|
1560
2130
|
"SKIP",
|
|
1561
|
-
|
|
2131
|
+
GEONOSIS_FILE2,
|
|
1562
2132
|
"no observability.endpoint was named, so whether the exporter is reachable was not asked"
|
|
1563
2133
|
);
|
|
1564
2134
|
}
|
|
@@ -1566,15 +2136,15 @@ var reachableFinding = async (config) => {
|
|
|
1566
2136
|
const timer = setTimeout(() => controller.abort(), HEAD_TIMEOUT_MS);
|
|
1567
2137
|
try {
|
|
1568
2138
|
const response = await fetch(endpoint, { method: "HEAD", signal: controller.signal });
|
|
1569
|
-
return
|
|
2139
|
+
return finding8(
|
|
1570
2140
|
"OK",
|
|
1571
|
-
|
|
2141
|
+
GEONOSIS_FILE2,
|
|
1572
2142
|
`${endpoint} is reachable \u2014 it answered ${response.status} to a HEAD`
|
|
1573
2143
|
);
|
|
1574
2144
|
} catch (error) {
|
|
1575
|
-
return
|
|
2145
|
+
return finding8(
|
|
1576
2146
|
"FAIL",
|
|
1577
|
-
|
|
2147
|
+
GEONOSIS_FILE2,
|
|
1578
2148
|
`${endpoint} is not reachable from here: ${error.message}. Every report this repo sends is going into that.`
|
|
1579
2149
|
);
|
|
1580
2150
|
} finally {
|
|
@@ -1584,36 +2154,36 @@ var reachableFinding = async (config) => {
|
|
|
1584
2154
|
var ageFinding = (config, root, now) => {
|
|
1585
2155
|
const file = config.lastEventFile;
|
|
1586
2156
|
if (typeof file !== "string" || file.trim() === "") {
|
|
1587
|
-
return
|
|
2157
|
+
return finding8(
|
|
1588
2158
|
"SKIP",
|
|
1589
|
-
|
|
2159
|
+
GEONOSIS_FILE2,
|
|
1590
2160
|
"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."
|
|
1591
2161
|
);
|
|
1592
2162
|
}
|
|
1593
2163
|
const maxAgeSeconds = typeof config.maxAgeSeconds === "number" && config.maxAgeSeconds > 0 ? config.maxAgeSeconds : DEFAULT_MAX_AGE_SECONDS;
|
|
1594
2164
|
let record;
|
|
1595
2165
|
try {
|
|
1596
|
-
record = JSON.parse(
|
|
2166
|
+
record = JSON.parse(readFileSync11(join11(root, file), "utf8"));
|
|
1597
2167
|
} catch (error) {
|
|
1598
|
-
return
|
|
2168
|
+
return finding8(
|
|
1599
2169
|
"FAIL",
|
|
1600
2170
|
file,
|
|
1601
2171
|
`the last event file could not be read: ${error.message}. A sink that has never written one has never captured anything.`
|
|
1602
2172
|
);
|
|
1603
2173
|
}
|
|
1604
2174
|
if (typeof record.at !== "number" || !Number.isFinite(record.at)) {
|
|
1605
|
-
return
|
|
2175
|
+
return finding8(
|
|
1606
2176
|
"FAIL",
|
|
1607
2177
|
file,
|
|
1608
2178
|
'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'
|
|
1609
2179
|
);
|
|
1610
2180
|
}
|
|
1611
2181
|
const ageSeconds = Math.round((now - record.at) / 1e3);
|
|
1612
|
-
return ageSeconds > maxAgeSeconds ?
|
|
2182
|
+
return ageSeconds > maxAgeSeconds ? finding8(
|
|
1613
2183
|
"FAIL",
|
|
1614
2184
|
file,
|
|
1615
2185
|
`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.`
|
|
1616
|
-
) :
|
|
2186
|
+
) : finding8(
|
|
1617
2187
|
"OK",
|
|
1618
2188
|
file,
|
|
1619
2189
|
`the last event arrived ${ageSeconds}s ago, inside the ${maxAgeSeconds}s window`
|
|
@@ -1622,18 +2192,18 @@ var ageFinding = (config, root, now) => {
|
|
|
1622
2192
|
var probeFinding = (config) => {
|
|
1623
2193
|
const probe = config.probe;
|
|
1624
2194
|
if (typeof probe === "string" && probe.trim() !== "") {
|
|
1625
|
-
return
|
|
2195
|
+
return finding8("OK", GEONOSIS_FILE2, `the probe that proves this exporter is "${probe}"`);
|
|
1626
2196
|
}
|
|
1627
2197
|
if (typeof config.lastEventFile === "string" && config.lastEventFile.trim() !== "") {
|
|
1628
|
-
return
|
|
2198
|
+
return finding8(
|
|
1629
2199
|
"OK",
|
|
1630
|
-
|
|
2200
|
+
GEONOSIS_FILE2,
|
|
1631
2201
|
"no probe command, but a last event file is read above, so something does look at this exporter"
|
|
1632
2202
|
);
|
|
1633
2203
|
}
|
|
1634
|
-
return
|
|
2204
|
+
return finding8(
|
|
1635
2205
|
"WARN",
|
|
1636
|
-
|
|
2206
|
+
GEONOSIS_FILE2,
|
|
1637
2207
|
"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."
|
|
1638
2208
|
);
|
|
1639
2209
|
};
|
|
@@ -1644,19 +2214,19 @@ var checkObservability = async ({
|
|
|
1644
2214
|
const read = readGeonosis2(root);
|
|
1645
2215
|
if (read.error !== void 0) {
|
|
1646
2216
|
return [
|
|
1647
|
-
|
|
2217
|
+
finding8(
|
|
1648
2218
|
"FAIL",
|
|
1649
|
-
|
|
1650
|
-
`${
|
|
2219
|
+
GEONOSIS_FILE2,
|
|
2220
|
+
`${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.`
|
|
1651
2221
|
)
|
|
1652
2222
|
];
|
|
1653
2223
|
}
|
|
1654
2224
|
if (!read.present || read.config === void 0) {
|
|
1655
2225
|
return [
|
|
1656
|
-
|
|
2226
|
+
finding8(
|
|
1657
2227
|
"SKIP",
|
|
1658
|
-
|
|
1659
|
-
`no observability block in ${
|
|
2228
|
+
GEONOSIS_FILE2,
|
|
2229
|
+
`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.`
|
|
1660
2230
|
)
|
|
1661
2231
|
];
|
|
1662
2232
|
}
|
|
@@ -1669,28 +2239,14 @@ var checkObservability = async ({
|
|
|
1669
2239
|
];
|
|
1670
2240
|
};
|
|
1671
2241
|
|
|
1672
|
-
// src/repo-corpus.ts
|
|
1673
|
-
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
1674
|
-
import { join as join7 } from "path";
|
|
1675
|
-
var GEONOSIS_FILE2 = "geonosis.json";
|
|
1676
|
-
var repoCorpusOf = (root) => {
|
|
1677
|
-
const path = join7(root, GEONOSIS_FILE2);
|
|
1678
|
-
if (!existsSync5(path)) return void 0;
|
|
1679
|
-
try {
|
|
1680
|
-
const parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
1681
|
-
const declared = parsed.doctor?.corpus;
|
|
1682
|
-
return typeof declared === "string" && declared !== "" ? join7(root, declared) : void 0;
|
|
1683
|
-
} catch {
|
|
1684
|
-
return void 0;
|
|
1685
|
-
}
|
|
1686
|
-
};
|
|
1687
|
-
|
|
1688
2242
|
// src/runner.ts
|
|
2243
|
+
import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync12 } from "fs";
|
|
2244
|
+
import { join as join12 } from "path";
|
|
1689
2245
|
var TEST_FAILURES = "testFailures";
|
|
1690
2246
|
var RUNS_A_RUNNER = /(?:^|[\s;&|(])(?:npx\s+|bunx\s+|pnpm\s+(?:exec\s+)?)?(?:vitest|bun\s+test)(?:\s|$)/;
|
|
1691
2247
|
var RUNS_BUN_TEST = /(?:^|[\s;&|(])(?:bunx\s+)?bun\s+test(?:\s|$)/;
|
|
1692
2248
|
var WRITES_A_REPORT = /--reporter[= ]\S*json|--outputFile/i;
|
|
1693
|
-
var
|
|
2249
|
+
var finding9 = (subject, verdict, message) => ({
|
|
1694
2250
|
check: "runner",
|
|
1695
2251
|
message,
|
|
1696
2252
|
subject,
|
|
@@ -1708,57 +2264,334 @@ var reportingCounters = (ratchet) => (ratchet?.counters ?? []).filter(
|
|
|
1708
2264
|
(entry) => entry.counter === TEST_FAILURES && stringOf(entry.report) !== ""
|
|
1709
2265
|
);
|
|
1710
2266
|
var keyOf = (entry) => stringOf(entry.key) === "" ? TEST_FAILURES : stringOf(entry.key);
|
|
2267
|
+
var PROVES = /--prove\b/;
|
|
2268
|
+
var everythingRun2 = (root, workspaces) => {
|
|
2269
|
+
const scripts = workspaces.flatMap((one) => Object.values(one.manifest.scripts ?? {}));
|
|
2270
|
+
const dir = join12(root, ".github/workflows");
|
|
2271
|
+
const workflows = (existsSync9(dir) ? readdirSync5(dir) : []).filter((name) => name.endsWith(".yml") || name.endsWith(".yaml")).map((name) => {
|
|
2272
|
+
try {
|
|
2273
|
+
return readFileSync12(join12(dir, name), "utf8");
|
|
2274
|
+
} catch {
|
|
2275
|
+
return "";
|
|
2276
|
+
}
|
|
2277
|
+
});
|
|
2278
|
+
let tiers = {};
|
|
2279
|
+
try {
|
|
2280
|
+
const config = JSON.parse(readFileSync12(join12(root, "geonosis.json"), "utf8"));
|
|
2281
|
+
tiers = config.verify ?? {};
|
|
2282
|
+
} catch {
|
|
2283
|
+
}
|
|
2284
|
+
return {
|
|
2285
|
+
run: [...Object.values(tiers).flat(), ...scripts, ...workflows].join("\n"),
|
|
2286
|
+
tiers: Object.keys(tiers)
|
|
2287
|
+
};
|
|
2288
|
+
};
|
|
2289
|
+
var proveIsWired = ({
|
|
2290
|
+
ratchet,
|
|
2291
|
+
root,
|
|
2292
|
+
workspaces
|
|
2293
|
+
}) => {
|
|
2294
|
+
if (ratchet === void 0 || ratchet.counters.length === 0) return [];
|
|
2295
|
+
const { run, tiers } = everythingRun2(root, workspaces);
|
|
2296
|
+
if (PROVES.test(run)) {
|
|
2297
|
+
return [
|
|
2298
|
+
finding9(
|
|
2299
|
+
RATCHET_FILE,
|
|
2300
|
+
"OK",
|
|
2301
|
+
`${ratchet.counters.length} counter(s), and something here runs geonosis-ratchet --prove`
|
|
2302
|
+
)
|
|
2303
|
+
];
|
|
2304
|
+
}
|
|
2305
|
+
const where = tiers.at(-1) ?? "full";
|
|
2306
|
+
return [
|
|
2307
|
+
finding9(
|
|
2308
|
+
RATCHET_FILE,
|
|
2309
|
+
"WARN",
|
|
2310
|
+
`${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`
|
|
2311
|
+
)
|
|
2312
|
+
];
|
|
2313
|
+
};
|
|
1711
2314
|
var checkRunner = ({
|
|
1712
2315
|
ratchet,
|
|
2316
|
+
root,
|
|
1713
2317
|
workspaces
|
|
1714
2318
|
}) => {
|
|
1715
2319
|
const reading = reportingCounters(ratchet);
|
|
1716
|
-
|
|
2320
|
+
const perWorkspace2 = workspaces.flatMap((workspace) => {
|
|
1717
2321
|
const script = stringOf(workspace.manifest.scripts?.test);
|
|
1718
2322
|
if (script === "") return [];
|
|
1719
2323
|
const subject = workspace.relative === "" ? "package.json" : `${workspace.relative}/package.json`;
|
|
1720
|
-
const
|
|
1721
|
-
|
|
2324
|
+
const said2 = (verdict, message) => [
|
|
2325
|
+
finding9(subject, verdict, message)
|
|
1722
2326
|
];
|
|
1723
2327
|
if (!RUNS_A_RUNNER.test(script)) {
|
|
1724
|
-
return
|
|
2328
|
+
return said2(
|
|
1725
2329
|
"OK",
|
|
1726
2330
|
`"${script}" runs neither vitest nor bun test \u2014 this check has nothing to say about it`
|
|
1727
2331
|
);
|
|
1728
2332
|
}
|
|
1729
2333
|
if (testFilesUnder(workspace.dir).length === 0) {
|
|
1730
|
-
return
|
|
2334
|
+
return said2(
|
|
1731
2335
|
"SKIP",
|
|
1732
2336
|
`"${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`
|
|
1733
2337
|
);
|
|
1734
2338
|
}
|
|
1735
2339
|
if (WRITES_A_REPORT.test(script)) {
|
|
1736
|
-
return
|
|
2340
|
+
return said2("OK", "the script asks the runner for its own JSON report, not for a status code");
|
|
1737
2341
|
}
|
|
1738
2342
|
const counter = reading.find((entry) => covers(stringOf(entry.command), workspace, workspaces));
|
|
1739
2343
|
if (counter !== void 0) {
|
|
1740
|
-
return
|
|
2344
|
+
return said2("OK", `read by the ratchet's "${keyOf(counter)}" counter in report mode`);
|
|
1741
2345
|
}
|
|
1742
2346
|
if (RUNS_BUN_TEST.test(script)) {
|
|
1743
|
-
return
|
|
2347
|
+
return said2(
|
|
1744
2348
|
"OK",
|
|
1745
2349
|
`"${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.`
|
|
1746
2350
|
);
|
|
1747
2351
|
}
|
|
1748
|
-
return
|
|
2352
|
+
return said2(
|
|
1749
2353
|
"WARN",
|
|
1750
2354
|
`"${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.`
|
|
1751
2355
|
);
|
|
1752
2356
|
});
|
|
2357
|
+
return [...proveIsWired({ ratchet, root, workspaces }), ...perWorkspace2];
|
|
1753
2358
|
};
|
|
1754
2359
|
|
|
1755
2360
|
// src/doctor.ts
|
|
2361
|
+
import { homedir as homedir2 } from "os";
|
|
2362
|
+
import { join as join14 } from "path";
|
|
1756
2363
|
import { resolveOxlint } from "@geonosis/lint-parity";
|
|
2364
|
+
|
|
2365
|
+
// src/engine.ts
|
|
2366
|
+
import { sep as sep4 } from "path";
|
|
2367
|
+
var declaredIn3 = (manifest) => new Set(
|
|
2368
|
+
[
|
|
2369
|
+
manifest.dependencies,
|
|
2370
|
+
manifest.devDependencies,
|
|
2371
|
+
manifest.optionalDependencies,
|
|
2372
|
+
manifest.peerDependencies
|
|
2373
|
+
].flatMap((block) => Object.keys(block ?? {}))
|
|
2374
|
+
);
|
|
2375
|
+
var holds3 = (parent, child) => child === parent || child.startsWith(`${parent}${sep4}`);
|
|
2376
|
+
var reachableFrom = (dir, workspaces) => new Set(
|
|
2377
|
+
workspaces.filter((one) => holds3(one.dir, dir)).flatMap((one) => [...declaredIn3(one.manifest)])
|
|
2378
|
+
);
|
|
2379
|
+
var enabledHere2 = (config, plugin) => [
|
|
2380
|
+
.../* @__PURE__ */ new Set([
|
|
2381
|
+
...enabledRulesOf(config.rules, plugin),
|
|
2382
|
+
...config.overrides.flatMap((one) => enabledRulesOf(one.rules, plugin))
|
|
2383
|
+
])
|
|
2384
|
+
];
|
|
2385
|
+
var checkEngines = async ({
|
|
2386
|
+
config,
|
|
2387
|
+
entry,
|
|
2388
|
+
workspaces
|
|
2389
|
+
}) => {
|
|
2390
|
+
const read = await presumptionsOf(entry).catch(() => void 0);
|
|
2391
|
+
if (read === void 0 || read.namespace === "" || Object.keys(read.presumed).length === 0) {
|
|
2392
|
+
return [];
|
|
2393
|
+
}
|
|
2394
|
+
const declared = reachableFrom(config.dir, workspaces);
|
|
2395
|
+
return enabledHere2(config, read.namespace).flatMap((rule) => {
|
|
2396
|
+
const presumes = read.presumed[rule.slice(read.namespace.length + 1)];
|
|
2397
|
+
if (presumes === void 0 || presumes.packages.some((name) => declared.has(name))) return [];
|
|
2398
|
+
return [
|
|
2399
|
+
{
|
|
2400
|
+
check: "exercised",
|
|
2401
|
+
message: `it presumes ${presumes.engine}, and no manifest from this config up to the root 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`,
|
|
2402
|
+
subject: `${config.relative} \u2192 ${rule}`,
|
|
2403
|
+
verdict: "WARN"
|
|
2404
|
+
}
|
|
2405
|
+
];
|
|
2406
|
+
});
|
|
2407
|
+
};
|
|
2408
|
+
|
|
2409
|
+
// src/rails.ts
|
|
2410
|
+
import { existsSync as existsSync10, readFileSync as readFileSync13 } from "fs";
|
|
2411
|
+
import { join as join13 } from "path";
|
|
2412
|
+
var GEONOSIS2 = "geonosis.json";
|
|
2413
|
+
var PROJECT = ".claude/settings.json";
|
|
2414
|
+
var LOCAL = ".claude/settings.local.json";
|
|
2415
|
+
var RUN_RECORD = ".geonosis/rails-run.json";
|
|
2416
|
+
var managedSettingsPath = (platform = process.platform) => platform === "darwin" ? "/Library/Application Support/ClaudeCode/managed-settings.json" : "/etc/claude-code/managed-settings.json";
|
|
2417
|
+
var finding10 = (subject, verdict, message) => ({
|
|
2418
|
+
check: "rails",
|
|
2419
|
+
message,
|
|
2420
|
+
subject,
|
|
2421
|
+
verdict
|
|
2422
|
+
});
|
|
2423
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2424
|
+
var networkIn = (parsed) => {
|
|
2425
|
+
if (!isRecord3(parsed)) return void 0;
|
|
2426
|
+
const sandbox = parsed["sandbox"];
|
|
2427
|
+
if (!isRecord3(sandbox)) return void 0;
|
|
2428
|
+
const network = sandbox["network"];
|
|
2429
|
+
if (!isRecord3(network)) return void 0;
|
|
2430
|
+
const allowed = network["allowedDomains"];
|
|
2431
|
+
return {
|
|
2432
|
+
allowedDomains: Array.isArray(allowed) ? allowed.filter((one) => typeof one === "string") : [],
|
|
2433
|
+
...network["allowManagedDomainsOnly"] === true ? { allowManagedDomainsOnly: true } : {}
|
|
2434
|
+
};
|
|
2435
|
+
};
|
|
2436
|
+
var sourceAt = (path, label) => {
|
|
2437
|
+
if (!existsSync10(path)) return { network: void 0, path: label, unreadable: false };
|
|
2438
|
+
try {
|
|
2439
|
+
return {
|
|
2440
|
+
network: networkIn(JSON.parse(readFileSync13(path, "utf8"))),
|
|
2441
|
+
path: label,
|
|
2442
|
+
unreadable: false
|
|
2443
|
+
};
|
|
2444
|
+
} catch {
|
|
2445
|
+
return { network: void 0, path: label, unreadable: true };
|
|
2446
|
+
}
|
|
2447
|
+
};
|
|
2448
|
+
var declaredEgress = (root) => {
|
|
2449
|
+
const at = join13(root, GEONOSIS2);
|
|
2450
|
+
if (!existsSync10(at)) return void 0;
|
|
2451
|
+
let parsed;
|
|
2452
|
+
try {
|
|
2453
|
+
parsed = JSON.parse(readFileSync13(at, "utf8"));
|
|
2454
|
+
} catch {
|
|
2455
|
+
return void 0;
|
|
2456
|
+
}
|
|
2457
|
+
const egress = parsed?.rails?.egress;
|
|
2458
|
+
if (!isRecord3(egress)) return void 0;
|
|
2459
|
+
const allow = egress["allow"];
|
|
2460
|
+
return {
|
|
2461
|
+
allow: Array.isArray(allow) ? allow.filter((one) => typeof one === "string") : []
|
|
2462
|
+
};
|
|
2463
|
+
};
|
|
2464
|
+
var deniedEgress = (root) => {
|
|
2465
|
+
const at = join13(root, RUN_RECORD);
|
|
2466
|
+
if (!existsSync10(at)) {
|
|
2467
|
+
return [
|
|
2468
|
+
finding10(
|
|
2469
|
+
"deniedEgress",
|
|
2470
|
+
"SKIP",
|
|
2471
|
+
`there is no ${RUN_RECORD} here, so no run has been recorded for this to be a gate over`
|
|
2472
|
+
)
|
|
2473
|
+
];
|
|
2474
|
+
}
|
|
2475
|
+
let parsed;
|
|
2476
|
+
try {
|
|
2477
|
+
parsed = JSON.parse(readFileSync13(at, "utf8"));
|
|
2478
|
+
} catch (error) {
|
|
2479
|
+
return [
|
|
2480
|
+
finding10(
|
|
2481
|
+
"deniedEgress",
|
|
2482
|
+
"FAIL",
|
|
2483
|
+
`${RUN_RECORD} is not readable JSON (${error.message}) \u2014 a record nobody can read is not a record of zero denied attempts`
|
|
2484
|
+
)
|
|
2485
|
+
];
|
|
2486
|
+
}
|
|
2487
|
+
const denied = isRecord3(parsed) && Array.isArray(parsed["deniedEgress"]) ? parsed["deniedEgress"] : [];
|
|
2488
|
+
if (denied.length === 0) {
|
|
2489
|
+
return [finding10("deniedEgress", "OK", "no run recorded a denied egress attempt")];
|
|
2490
|
+
}
|
|
2491
|
+
const hosts = denied.map((one) => isRecord3(one) && typeof one["host"] === "string" ? one["host"] : "(unnamed)").join(", ");
|
|
2492
|
+
return [
|
|
2493
|
+
finding10(
|
|
2494
|
+
"deniedEgress",
|
|
2495
|
+
"FAIL",
|
|
2496
|
+
`${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`
|
|
2497
|
+
)
|
|
2498
|
+
];
|
|
2499
|
+
};
|
|
2500
|
+
var checkRails = ({
|
|
2501
|
+
managedSettings = managedSettingsPath(),
|
|
2502
|
+
root,
|
|
2503
|
+
userSettings
|
|
2504
|
+
}) => {
|
|
2505
|
+
const declared = declaredEgress(root);
|
|
2506
|
+
if (declared === void 0 || declared.allow.length === 0) {
|
|
2507
|
+
return [
|
|
2508
|
+
finding10(
|
|
2509
|
+
`${GEONOSIS2} \u2192 rails.egress`,
|
|
2510
|
+
"SKIP",
|
|
2511
|
+
"this repo declares no egress allowlist, so there is no rendered setting for this to read back"
|
|
2512
|
+
)
|
|
2513
|
+
];
|
|
2514
|
+
}
|
|
2515
|
+
const sources = [
|
|
2516
|
+
sourceAt(managedSettings, managedSettings),
|
|
2517
|
+
sourceAt(userSettings, userSettings),
|
|
2518
|
+
sourceAt(join13(root, PROJECT), PROJECT),
|
|
2519
|
+
sourceAt(join13(root, LOCAL), LOCAL)
|
|
2520
|
+
];
|
|
2521
|
+
const unreadable = sources.filter((one) => one.unreadable).map(
|
|
2522
|
+
(one) => finding10(
|
|
2523
|
+
one.path,
|
|
2524
|
+
"FAIL",
|
|
2525
|
+
"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"
|
|
2526
|
+
)
|
|
2527
|
+
);
|
|
2528
|
+
const managedNetwork = sources[0]?.network;
|
|
2529
|
+
const onlyManaged = managedNetwork?.allowManagedDomainsOnly === true;
|
|
2530
|
+
const effective = new Set(
|
|
2531
|
+
(onlyManaged ? [sources[0]] : sources).flatMap((one) => one?.network?.allowedDomains ?? [])
|
|
2532
|
+
);
|
|
2533
|
+
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`;
|
|
2534
|
+
return [
|
|
2535
|
+
...unreadable,
|
|
2536
|
+
...declared.allow.map(
|
|
2537
|
+
(domain) => effective.has(domain) ? finding10(domain, "OK", "declared, and in the settings that actually load") : finding10(
|
|
2538
|
+
domain,
|
|
2539
|
+
"FAIL",
|
|
2540
|
+
`declared in ${GEONOSIS2} \u2192 rails.egress and absent from every settings file that loads, so the run has no allowance for it.${why}`
|
|
2541
|
+
)
|
|
2542
|
+
),
|
|
2543
|
+
...deniedEgress(root)
|
|
2544
|
+
];
|
|
2545
|
+
};
|
|
2546
|
+
|
|
2547
|
+
// src/required-options.ts
|
|
2548
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2549
|
+
var HEADER = /requires option `([^`]+)`/;
|
|
2550
|
+
var optionsOf2 = (level) => Array.isArray(level) ? level.slice(1) : [];
|
|
2551
|
+
var missingOption = (error) => {
|
|
2552
|
+
const named2 = error.option;
|
|
2553
|
+
return typeof named2 === "string" ? named2 : HEADER.exec(String(error.message))?.[1];
|
|
2554
|
+
};
|
|
2555
|
+
var configured = (config, namespace) => [config.rules, ...config.overrides.map((one) => one.rules)].flatMap(
|
|
2556
|
+
(rules) => enabledRulesOf(rules, namespace).map((id) => [id, optionsOf2(rules[id])])
|
|
2557
|
+
);
|
|
2558
|
+
var checkRequiredOptions = async ({
|
|
2559
|
+
config,
|
|
2560
|
+
entry
|
|
2561
|
+
}) => {
|
|
2562
|
+
const loaded = await import(pathToFileURL2(entry).href).catch(() => void 0);
|
|
2563
|
+
const namespace = loaded?.default?.meta?.name;
|
|
2564
|
+
const rules = loaded?.default?.rules;
|
|
2565
|
+
if (typeof namespace !== "string" || namespace === "" || rules === void 0) return [];
|
|
2566
|
+
return configured(config, namespace).flatMap(([id, options]) => {
|
|
2567
|
+
const rule = rules[id.slice(namespace.length + 1)];
|
|
2568
|
+
if (rule === void 0) return [];
|
|
2569
|
+
try {
|
|
2570
|
+
rule.create({ options, report: () => {
|
|
2571
|
+
} });
|
|
2572
|
+
return [];
|
|
2573
|
+
} catch (error) {
|
|
2574
|
+
const option = missingOption(error);
|
|
2575
|
+
if (option === void 0) return [];
|
|
2576
|
+
return [
|
|
2577
|
+
{
|
|
2578
|
+
check: "loaded",
|
|
2579
|
+
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`,
|
|
2580
|
+
subject: `${config.relative} \u2192 ${id}`,
|
|
2581
|
+
verdict: "FAIL"
|
|
2582
|
+
}
|
|
2583
|
+
];
|
|
2584
|
+
}
|
|
2585
|
+
});
|
|
2586
|
+
};
|
|
2587
|
+
|
|
2588
|
+
// src/doctor.ts
|
|
1757
2589
|
var exercisedOf = ({
|
|
1758
2590
|
configs,
|
|
1759
2591
|
oxlint,
|
|
1760
2592
|
repoCorpus,
|
|
1761
|
-
root
|
|
2593
|
+
root,
|
|
2594
|
+
workspaces
|
|
1762
2595
|
}) => Promise.all(
|
|
1763
2596
|
configs.flatMap(
|
|
1764
2597
|
(config) => config.jsPlugins.filter((name) => name.startsWith(SCOPE)).map(async (specifier) => {
|
|
@@ -1768,22 +2601,33 @@ var exercisedOf = ({
|
|
|
1768
2601
|
corpus = corpusOfPlugin(config.dir, specifier);
|
|
1769
2602
|
entry = resolveFrom(config.dir, specifier);
|
|
1770
2603
|
} catch (error) {
|
|
1771
|
-
return
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
2604
|
+
return [
|
|
2605
|
+
{
|
|
2606
|
+
check: "exercised",
|
|
2607
|
+
message: `${specifier}: ${String(error.message)}`,
|
|
2608
|
+
subject: config.relative,
|
|
2609
|
+
verdict: "SKIP"
|
|
2610
|
+
}
|
|
2611
|
+
];
|
|
1777
2612
|
}
|
|
1778
2613
|
const own = repoCorpus !== void 0 && config.relative === CONFIG_FILE ? repoCorpus : void 0;
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
2614
|
+
const [reach, engines, options] = await Promise.all([
|
|
2615
|
+
checkExercised({
|
|
2616
|
+
config,
|
|
2617
|
+
corpus,
|
|
2618
|
+
entry,
|
|
2619
|
+
oxlint,
|
|
2620
|
+
...own === void 0 ? {} : { repoCorpus: own },
|
|
2621
|
+
root
|
|
2622
|
+
}),
|
|
2623
|
+
// #159: the rules whose engine this tree has not got. Beside the reach line rather than
|
|
2624
|
+
// inside it — one is about the corpus, the other about this repo's own manifests.
|
|
2625
|
+
checkEngines({ config, entry, workspaces }),
|
|
2626
|
+
// #202: and the rules this config enables without an option they require, which the
|
|
2627
|
+
// lint can only report as a run it could not make.
|
|
2628
|
+
checkRequiredOptions({ config, entry })
|
|
2629
|
+
]);
|
|
2630
|
+
return [reach, ...engines, ...options];
|
|
1787
2631
|
})
|
|
1788
2632
|
)
|
|
1789
2633
|
);
|
|
@@ -1802,6 +2646,7 @@ var baselineOf = ({
|
|
|
1802
2646
|
const ref = baseline.ref ?? defaultRef(root);
|
|
1803
2647
|
return ref === void 0 ? skip("no ref was named and this repo has no origin/main to fall back on") : checkBaseline({ ref, root });
|
|
1804
2648
|
};
|
|
2649
|
+
var USER_SETTINGS = ".claude/settings.json";
|
|
1805
2650
|
var ordered = (findings) => CHECKS.flatMap((check) => findings.filter((one) => one.check === check));
|
|
1806
2651
|
var EMPTY = { FAIL: 0, OK: 0, SKIP: 0, UNJUDGED: 0, WARN: 0 };
|
|
1807
2652
|
var countsOf = (findings) => findings.reduce((counts, one) => ({ ...counts, [one.verdict]: counts[one.verdict] + 1 }), {
|
|
@@ -1824,6 +2669,17 @@ var runDoctor = async ({
|
|
|
1824
2669
|
const asked = (check) => only === void 0 || only.includes(check);
|
|
1825
2670
|
const runs = [
|
|
1826
2671
|
["loaded", () => checkLoaded({ configs, root, workspaces })],
|
|
2672
|
+
[
|
|
2673
|
+
"group",
|
|
2674
|
+
() => {
|
|
2675
|
+
const declared = declaredGroupsOf(root);
|
|
2676
|
+
return checkGroup({
|
|
2677
|
+
...declared === void 0 ? {} : { groups: declared },
|
|
2678
|
+
root,
|
|
2679
|
+
workspaces
|
|
2680
|
+
});
|
|
2681
|
+
}
|
|
2682
|
+
],
|
|
1827
2683
|
[
|
|
1828
2684
|
"exercised",
|
|
1829
2685
|
() => {
|
|
@@ -1832,15 +2688,18 @@ var runDoctor = async ({
|
|
|
1832
2688
|
configs,
|
|
1833
2689
|
oxlint: oxlint ?? resolveOxlint(root),
|
|
1834
2690
|
...repoCorpus === void 0 ? {} : { repoCorpus },
|
|
1835
|
-
root
|
|
1836
|
-
|
|
2691
|
+
root,
|
|
2692
|
+
workspaces
|
|
2693
|
+
}).then((found2) => found2.flat());
|
|
1837
2694
|
}
|
|
1838
2695
|
],
|
|
1839
2696
|
["baseline", () => baselineOf({ baseline, root })],
|
|
1840
|
-
["runner", () => checkRunner({ ratchet: readRatchet(root), workspaces })],
|
|
2697
|
+
["runner", () => checkRunner({ ratchet: readRatchet(root), root, workspaces })],
|
|
2698
|
+
["envelope", () => checkEnvelopes({ root })],
|
|
1841
2699
|
["observability", () => checkObservability({ now: Date.now(), root })],
|
|
1842
2700
|
["drift", () => checkDrift({ root, workspaces })],
|
|
1843
|
-
["deployed", () => checkDeployed({ root })]
|
|
2701
|
+
["deployed", () => checkDeployed({ root })],
|
|
2702
|
+
["rails", () => checkRails({ root, userSettings: join14(homedir2(), USER_SETTINGS) })]
|
|
1844
2703
|
];
|
|
1845
2704
|
const collected = [];
|
|
1846
2705
|
for (const [check, run] of runs) if (asked(check)) collected.push(...await run());
|
|
@@ -1859,9 +2718,12 @@ var ABOUT = {
|
|
|
1859
2718
|
baseline: "a number that may only shrink, against another ref",
|
|
1860
2719
|
deployed: "what the pipeline reported deploying is what the tree declares",
|
|
1861
2720
|
drift: "the gates that were set up and are no longer running",
|
|
2721
|
+
envelope: "every gate read as many things as it was handed",
|
|
1862
2722
|
exercised: "every enabled rule fires on at least one corpus file",
|
|
2723
|
+
group: "the packages published on one version resolve to one version",
|
|
1863
2724
|
loaded: "the plugin oxlint would load is the one the manifest pins",
|
|
1864
2725
|
observability: "an exporter is configured, reachable, and something arrived through it lately",
|
|
2726
|
+
rails: "the egress a run is bounded by is in the settings that actually load",
|
|
1865
2727
|
runner: "something reads the test runner\u2019s own report, not its exit code"
|
|
1866
2728
|
};
|
|
1867
2729
|
var WIDTH = 8;
|
|
@@ -1910,17 +2772,26 @@ export {
|
|
|
1910
2772
|
pluginVersionOf,
|
|
1911
2773
|
corpusOfPlugin,
|
|
1912
2774
|
relativeToRoot,
|
|
2775
|
+
COMPOSITION_ROOT,
|
|
2776
|
+
FLOOR_PACKAGES,
|
|
1913
2777
|
READERS,
|
|
1914
2778
|
checkDrift,
|
|
1915
2779
|
enabledRulesOf,
|
|
1916
2780
|
checkExercised,
|
|
2781
|
+
ENVELOPES_DIR,
|
|
2782
|
+
NO_ENVELOPES,
|
|
2783
|
+
checkEnvelopes,
|
|
2784
|
+
GEONOSIS_FILE,
|
|
2785
|
+
repoCorpusOf,
|
|
2786
|
+
FIXED_GROUP,
|
|
2787
|
+
KIT_GROUP,
|
|
2788
|
+
declaredGroupsOf,
|
|
2789
|
+
checkGroup,
|
|
1917
2790
|
SCOPE,
|
|
1918
2791
|
satisfies,
|
|
1919
2792
|
declaredFor,
|
|
1920
2793
|
checkLoaded,
|
|
1921
2794
|
checkObservability,
|
|
1922
|
-
GEONOSIS_FILE2 as GEONOSIS_FILE,
|
|
1923
|
-
repoCorpusOf,
|
|
1924
2795
|
checkRunner,
|
|
1925
2796
|
runDoctor,
|
|
1926
2797
|
formatDoctor,
|