@trofimyukdev/repo-truth 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +273 -2
  3. package/dist/action.d.ts +52 -0
  4. package/dist/action.js +182 -0
  5. package/dist/action.js.map +1 -0
  6. package/dist/checks/action-pins.d.ts +52 -0
  7. package/dist/checks/action-pins.js +196 -0
  8. package/dist/checks/action-pins.js.map +1 -0
  9. package/dist/checks/autosquash.d.ts +23 -0
  10. package/dist/checks/autosquash.js +85 -0
  11. package/dist/checks/autosquash.js.map +1 -0
  12. package/dist/checks/bidi-controls.d.ts +58 -0
  13. package/dist/checks/bidi-controls.js +172 -0
  14. package/dist/checks/bidi-controls.js.map +1 -0
  15. package/dist/checks/binary-files.d.ts +53 -0
  16. package/dist/checks/binary-files.js +157 -0
  17. package/dist/checks/binary-files.js.map +1 -0
  18. package/dist/checks/config-changes.d.ts +57 -0
  19. package/dist/checks/config-changes.js +175 -0
  20. package/dist/checks/config-changes.js.map +1 -0
  21. package/dist/checks/credentials.d.ts +35 -0
  22. package/dist/checks/credentials.js +138 -0
  23. package/dist/checks/credentials.js.map +1 -0
  24. package/dist/checks/install-scripts.d.ts +39 -0
  25. package/dist/checks/install-scripts.js +173 -0
  26. package/dist/checks/install-scripts.js.map +1 -0
  27. package/dist/checks/lint-rules.d.ts +49 -0
  28. package/dist/checks/lint-rules.js +184 -0
  29. package/dist/checks/lint-rules.js.map +1 -0
  30. package/dist/checks/path-policy.d.ts +40 -0
  31. package/dist/checks/path-policy.js +195 -0
  32. package/dist/checks/path-policy.js.map +1 -0
  33. package/dist/checks/raised-timeouts.d.ts +66 -0
  34. package/dist/checks/raised-timeouts.js +248 -0
  35. package/dist/checks/raised-timeouts.js.map +1 -0
  36. package/dist/checks/script-changes.d.ts +51 -0
  37. package/dist/checks/script-changes.js +199 -0
  38. package/dist/checks/script-changes.js.map +1 -0
  39. package/dist/checks/secret-files.d.ts +58 -0
  40. package/dist/checks/secret-files.js +165 -0
  41. package/dist/checks/secret-files.js.map +1 -0
  42. package/dist/checks/strictness.d.ts +59 -0
  43. package/dist/checks/strictness.js +260 -0
  44. package/dist/checks/strictness.js.map +1 -0
  45. package/dist/checks/submodules.d.ts +51 -0
  46. package/dist/checks/submodules.js +172 -0
  47. package/dist/checks/submodules.js.map +1 -0
  48. package/dist/checks/suppressions.d.ts +53 -0
  49. package/dist/checks/suppressions.js +203 -0
  50. package/dist/checks/suppressions.js.map +1 -0
  51. package/dist/checks/workflow-permissions.d.ts +56 -0
  52. package/dist/checks/workflow-permissions.js +237 -0
  53. package/dist/checks/workflow-permissions.js.map +1 -0
  54. package/dist/checks/workflow-weakening.d.ts +38 -0
  55. package/dist/checks/workflow-weakening.js +172 -0
  56. package/dist/checks/workflow-weakening.js.map +1 -0
  57. package/dist/cli.d.ts +31 -0
  58. package/dist/cli.js +300 -0
  59. package/dist/cli.js.map +1 -0
  60. package/dist/commit-range.d.ts +28 -0
  61. package/dist/commit-range.js +152 -0
  62. package/dist/commit-range.js.map +1 -0
  63. package/dist/config.d.ts +35 -0
  64. package/dist/config.js +235 -0
  65. package/dist/config.js.map +1 -0
  66. package/dist/conflict-markers.d.ts +40 -0
  67. package/dist/conflict-markers.js +234 -0
  68. package/dist/conflict-markers.js.map +1 -0
  69. package/dist/dependency-sources.d.ts +32 -0
  70. package/dist/dependency-sources.js +260 -0
  71. package/dist/dependency-sources.js.map +1 -0
  72. package/dist/figure-claims.d.ts +35 -0
  73. package/dist/figure-claims.js +139 -0
  74. package/dist/figure-claims.js.map +1 -0
  75. package/dist/figure-text.d.ts +22 -0
  76. package/dist/figure-text.js +119 -0
  77. package/dist/figure-text.js.map +1 -0
  78. package/dist/index.d.ts +46 -0
  79. package/dist/index.js +38 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/large-blobs.d.ts +35 -0
  82. package/dist/large-blobs.js +163 -0
  83. package/dist/large-blobs.js.map +1 -0
  84. package/dist/lockfile-drift.d.ts +36 -0
  85. package/dist/lockfile-drift.js +206 -0
  86. package/dist/lockfile-drift.js.map +1 -0
  87. package/dist/lowered-thresholds.d.ts +70 -0
  88. package/dist/lowered-thresholds.js +281 -0
  89. package/dist/lowered-thresholds.js.map +1 -0
  90. package/dist/measurement-rule.d.ts +39 -0
  91. package/dist/measurement-rule.js +213 -0
  92. package/dist/measurement-rule.js.map +1 -0
  93. package/dist/portable-paths.d.ts +37 -0
  94. package/dist/portable-paths.js +215 -0
  95. package/dist/portable-paths.js.map +1 -0
  96. package/dist/registry.d.ts +43 -0
  97. package/dist/registry.js +144 -0
  98. package/dist/registry.js.map +1 -0
  99. package/dist/replay-plan.d.ts +36 -0
  100. package/dist/replay-plan.js +196 -0
  101. package/dist/replay-plan.js.map +1 -0
  102. package/dist/replayed-figures.d.ts +36 -0
  103. package/dist/replayed-figures.js +159 -0
  104. package/dist/replayed-figures.js.map +1 -0
  105. package/dist/snapshot-refreshes.d.ts +36 -0
  106. package/dist/snapshot-refreshes.js +202 -0
  107. package/dist/snapshot-refreshes.js.map +1 -0
  108. package/dist/stray-files.d.ts +52 -0
  109. package/dist/stray-files.js +234 -0
  110. package/dist/stray-files.js.map +1 -0
  111. package/dist/symlinks.d.ts +28 -0
  112. package/dist/symlinks.js +193 -0
  113. package/dist/symlinks.js.map +1 -0
  114. package/dist/trailers.d.ts +36 -0
  115. package/dist/trailers.js +110 -0
  116. package/dist/trailers.js.map +1 -0
  117. package/dist/weakened-tests.d.ts +94 -0
  118. package/dist/weakened-tests.js +458 -0
  119. package/dist/weakened-tests.js.map +1 -0
  120. package/package.json +31 -4
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Install scripts added.
3
+ *
4
+ * `npm ci` and `npm install` run a package's `preinstall`, `install` and
5
+ * `postinstall` scripts on the machine that installs it, and npm's lockfile
6
+ * says which packages have one: the entry of such a package under `packages`
7
+ * holds `"hasInstallScript": true`. This check reads every file the range adds,
8
+ * changes or renames onto whose last path segment is exactly `package-lock.json`
9
+ * or `npm-shrinkwrap.json`, at any depth, whole through git at the merge base
10
+ * of `base` and `candidate` and at `candidate` (never the working tree, renames
11
+ * detected), and parses each with `JSON.parse`. The source is the file at the
12
+ * merge base, the one a rename came from, or none for a file the range adds.
13
+ *
14
+ * A finding is a key of the candidate's `packages` object whose entry holds
15
+ * `hasInstallScript` equal to `true` while the source's `packages` does not
16
+ * hold that key with such an entry (whatever its version). A lockfile of
17
+ * version 1 has no `packages` object and yields nothing. A lockfile, candidate
18
+ * or source, that `JSON.parse` refuses fails the record and names the file.
19
+ *
20
+ * A finding's id is `install-script:<file>`, which sanctions every finding of
21
+ * that file, by a `Sanctioned-Weakening: <id> <reason>` line in a commit
22
+ * message of the range or by the caller's `sanctions`.
23
+ */
24
+ import { execFile } from "node:child_process";
25
+ import { SANCTION_KEY } from "../weakened-tests.js";
26
+ export const name = "RT-94";
27
+ function runGit(cwd, args) {
28
+ return new Promise((resolve, reject) => {
29
+ execFile("git", args, { cwd, maxBuffer: 1024 * 1024 * 1024, encoding: "utf8" }, (error, stdout, stderr) => {
30
+ if (error)
31
+ reject(new Error(stderr.trim() || error.message));
32
+ else
33
+ resolve(stdout);
34
+ });
35
+ });
36
+ }
37
+ /** Escape everything outside printable ASCII, so evidence cannot replay a control sequence. */
38
+ function escapeText(text) {
39
+ let escaped = "";
40
+ for (const char of text) {
41
+ const codePoint = char.codePointAt(0) ?? 0;
42
+ escaped += codePoint >= 0x20 && codePoint <= 0x7e ? char : `\\u{${codePoint.toString(16).padStart(4, "0")}}`;
43
+ }
44
+ return escaped;
45
+ }
46
+ async function resolveCommit(cwd, revision) {
47
+ const fail = (cause) => new Error(`revision "${escapeText(String(revision))}" does not name a commit${cause}`);
48
+ // A revision that begins with a dash would be read as an option.
49
+ if (typeof revision !== "string" || revision.length === 0 || revision.startsWith("-"))
50
+ throw fail("");
51
+ let hash = "";
52
+ try {
53
+ hash = (await runGit(cwd, ["rev-parse", "--verify", "--quiet", `${revision}^{commit}`])).trim();
54
+ }
55
+ catch (error) {
56
+ throw fail(`: ${error.message}`);
57
+ }
58
+ if (hash.length === 0)
59
+ throw fail("");
60
+ return hash;
61
+ }
62
+ const isLockfile = (path) => /(^|\/)(package-lock|npm-shrinkwrap)\.json$/.test(path);
63
+ const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
64
+ async function readPackages(cwd, mode, sha) {
65
+ if (!mode.startsWith("100"))
66
+ return { problem: "is not a regular file" };
67
+ let parsed;
68
+ try {
69
+ parsed = JSON.parse(await runGit(cwd, ["cat-file", "blob", sha]));
70
+ }
71
+ catch (error) {
72
+ return { problem: `is not JSON: ${error.message}` };
73
+ }
74
+ const packages = isObject(parsed) && Object.hasOwn(parsed, "packages") ? parsed["packages"] : null;
75
+ return { packages: isObject(packages) ? packages : null };
76
+ }
77
+ /** Whether an entry of `packages` is an object holding `hasInstallScript: true`. */
78
+ const flagged = (entry) => isObject(entry) && entry["hasInstallScript"] === true;
79
+ async function compare(cwd, from, to) {
80
+ const out = await runGit(cwd, ["diff", "--raw", "-z", "--no-abbrev", "--find-renames", "--no-relative", from, to, "--"]);
81
+ const fields = out.split("\0");
82
+ const drafts = [];
83
+ const problems = [];
84
+ for (let i = 0; i < fields.length; i++) {
85
+ const head = fields[i] ?? "";
86
+ if (!head.startsWith(":"))
87
+ continue;
88
+ const [oldMode = "", newMode = "", oldSha = "", newSha = "", status = ""] = head.slice(1).split(" ");
89
+ const first = fields[++i] ?? "";
90
+ const renamed = status.startsWith("R") || status.startsWith("C");
91
+ const second = renamed ? (fields[++i] ?? "") : first;
92
+ if (status.startsWith("D"))
93
+ continue;
94
+ const oldPath = status.startsWith("A") ? null : first;
95
+ const file = second;
96
+ if (!isLockfile(file))
97
+ continue;
98
+ const after = await readPackages(cwd, newMode, newSha);
99
+ const before = oldPath === null ? { packages: null } : await readPackages(cwd, oldMode, oldSha);
100
+ if ("problem" in after)
101
+ problems.push(`${escapeText(file)}: at the candidate the lockfile ${escapeText(after.problem)}`);
102
+ if ("problem" in before)
103
+ problems.push(`${escapeText(oldPath ?? file)}: at the merge base the lockfile ${escapeText(before.problem)}`);
104
+ if ("problem" in after || "problem" in before || after.packages === null)
105
+ continue;
106
+ for (const [key, entry] of Object.entries(after.packages)) {
107
+ if (!flagged(entry))
108
+ continue;
109
+ if (before.packages !== null && Object.hasOwn(before.packages, key) && flagged(before.packages[key]))
110
+ continue;
111
+ const version = entry["version"];
112
+ drafts.push({ file, text: `${escapeText(file)}: install-script ${escapeText(key)}${typeof version === "string" ? `@${escapeText(version)}` : ""}` });
113
+ }
114
+ }
115
+ return { drafts, problems };
116
+ }
117
+ /** The text after the sanction key on every such line of the range's commit messages. */
118
+ async function readSanctionLines(cwd, from, to) {
119
+ const out = await runGit(cwd, ["log", "--format=%B%x00", `${from}..${to}`, "--"]);
120
+ const prefix = `${SANCTION_KEY}:`;
121
+ return out
122
+ .split(/[\n\0]/)
123
+ .map((line) => line.trim())
124
+ .filter((line) => line.startsWith(prefix))
125
+ .map((line) => line.slice(prefix.length).trim());
126
+ }
127
+ function sanctionFor(id, lines, given) {
128
+ const reason = Object.hasOwn(given, id) ? given[id] : undefined;
129
+ if (typeof reason === "string" && reason.trim().length > 0)
130
+ return reason.trim();
131
+ for (const line of lines) {
132
+ const rest = line.slice(id.length);
133
+ if (line.startsWith(id) && /^\s+\S/.test(rest))
134
+ return rest.trim();
135
+ }
136
+ return null;
137
+ }
138
+ /**
139
+ * Report each package `base..candidate` adds to a lockfile with an install
140
+ * script. Fails while any finding is unsanctioned or a lockfile cannot be read,
141
+ * and fails closed: a revision that names no commit, or any git invocation that
142
+ * fails, is a `fail` record naming the cause - never a `pass`, never a throw.
143
+ */
144
+ export async function check(options) {
145
+ try {
146
+ const { cwd, base, candidate } = options;
147
+ const baseCommit = await resolveCommit(cwd, base);
148
+ const candidateCommit = await resolveCommit(cwd, candidate);
149
+ let ancestor;
150
+ try {
151
+ ancestor = (await runGit(cwd, ["merge-base", baseCommit, candidateCommit])).trim();
152
+ }
153
+ catch (error) {
154
+ throw new Error(`no merge base of "${escapeText(base)}" and "${escapeText(candidate)}": ${error.message}`);
155
+ }
156
+ const { drafts, problems } = await compare(cwd, ancestor, candidateCommit);
157
+ if (drafts.length === 0 && problems.length === 0)
158
+ return { name, status: "pass", evidence: ["no install script added"] };
159
+ const lines = drafts.length === 0 ? [] : await readSanctionLines(cwd, ancestor, candidateCommit);
160
+ let open = problems.length > 0;
161
+ const evidence = drafts.map((draft) => {
162
+ const reason = sanctionFor(`install-script:${draft.file}`, lines, options.sanctions ?? {});
163
+ if (reason === null)
164
+ open = true;
165
+ return reason === null ? draft.text : `${draft.text} - sanctioned: ${escapeText(reason)}`;
166
+ });
167
+ return { name, status: open ? "fail" : "pass", evidence: [...problems, ...evidence] };
168
+ }
169
+ catch (error) {
170
+ return { name, status: "fail", evidence: [escapeText(error instanceof Error ? error.message : String(error))] };
171
+ }
172
+ }
173
+ //# sourceMappingURL=install-scripts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-scripts.js","sourceRoot":"","sources":["../../src/checks/install-scripts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Lint rules turned off.
3
+ *
4
+ * A lint run goes green as surely when a rule is turned off in the lint
5
+ * configuration as when the code is fixed. This check reads every path the range
6
+ * adds, changes or renames onto whose last path segment is exactly one of
7
+ * `LINT_FILES` (`.eslintrc*`, `eslint.config.*`, `biome.json`, `biome.jsonc`,
8
+ * `.oxlintrc.json`), at any depth, whole and through git at the merge base of
9
+ * `base` and `candidate` and at `candidate`, renames detected. The working tree
10
+ * is never read, and no file is parsed or run.
11
+ *
12
+ * An off pair is, in a file's text, a rule name, white space or none, a colon,
13
+ * white space or none, then an off value (`"off"`, `'off'`, `off` or `0`, or
14
+ * `[` and white space before one of them), the value not followed by a letter, a
15
+ * digit, `_` or `.`. A name is written in double quotes, in single quotes or
16
+ * bare. A pair is counted wherever it stands, a comment included. For each file,
17
+ * each name's pairs are counted at the candidate and in the source file (the
18
+ * file at the merge base, the one a rename came from, or none for a file the
19
+ * range adds); a name counted more often at the candidate is one finding
20
+ * `rule-off`, whatever the count. A rule moved or a line re-indented says
21
+ * nothing; a rule changed from `error` or `warn` to `off` is a finding.
22
+ *
23
+ * Each finding is the evidence line `<file>: rule-off <name>`. A finding's id is
24
+ * `rule-off:<file>`, which sanctions every finding of that file by a
25
+ * `Sanctioned-Weakening: <id> <reason>` line in a commit message of the range or
26
+ * by the caller's `sanctions`; the evidence line then ends ` - sanctioned:
27
+ * <reason>`.
28
+ *
29
+ * What it does not see: a rule set to `warn`, a rule deleted, a rule turned off
30
+ * by a configuration named in `extends`, `package.json`'s `eslintConfig`, ignore
31
+ * patterns, biome's `"recommended": false` and `"enabled": false`, and other
32
+ * linters' files.
33
+ */
34
+ import type { CheckRecord } from "../index.js";
35
+ export declare const name = "RT-92";
36
+ export interface LintRulesOptions {
37
+ readonly cwd: string;
38
+ readonly base: string;
39
+ readonly candidate: string;
40
+ /** Ids (`rule-off:<file>`) mapped to reasons. */
41
+ readonly sanctions?: Readonly<Record<string, string>> | undefined;
42
+ }
43
+ /**
44
+ * Report each rule `base..candidate` turns off in a lint configuration file.
45
+ * Fails while any finding is unsanctioned, and fails closed: a revision that
46
+ * names no commit, or any git invocation that fails, is a `fail` record naming
47
+ * the cause - never a `pass`, never a throw.
48
+ */
49
+ export declare function check(options: LintRulesOptions): Promise<CheckRecord>;
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Lint rules turned off.
3
+ *
4
+ * A lint run goes green as surely when a rule is turned off in the lint
5
+ * configuration as when the code is fixed. This check reads every path the range
6
+ * adds, changes or renames onto whose last path segment is exactly one of
7
+ * `LINT_FILES` (`.eslintrc*`, `eslint.config.*`, `biome.json`, `biome.jsonc`,
8
+ * `.oxlintrc.json`), at any depth, whole and through git at the merge base of
9
+ * `base` and `candidate` and at `candidate`, renames detected. The working tree
10
+ * is never read, and no file is parsed or run.
11
+ *
12
+ * An off pair is, in a file's text, a rule name, white space or none, a colon,
13
+ * white space or none, then an off value (`"off"`, `'off'`, `off` or `0`, or
14
+ * `[` and white space before one of them), the value not followed by a letter, a
15
+ * digit, `_` or `.`. A name is written in double quotes, in single quotes or
16
+ * bare. A pair is counted wherever it stands, a comment included. For each file,
17
+ * each name's pairs are counted at the candidate and in the source file (the
18
+ * file at the merge base, the one a rename came from, or none for a file the
19
+ * range adds); a name counted more often at the candidate is one finding
20
+ * `rule-off`, whatever the count. A rule moved or a line re-indented says
21
+ * nothing; a rule changed from `error` or `warn` to `off` is a finding.
22
+ *
23
+ * Each finding is the evidence line `<file>: rule-off <name>`. A finding's id is
24
+ * `rule-off:<file>`, which sanctions every finding of that file by a
25
+ * `Sanctioned-Weakening: <id> <reason>` line in a commit message of the range or
26
+ * by the caller's `sanctions`; the evidence line then ends ` - sanctioned:
27
+ * <reason>`.
28
+ *
29
+ * What it does not see: a rule set to `warn`, a rule deleted, a rule turned off
30
+ * by a configuration named in `extends`, `package.json`'s `eslintConfig`, ignore
31
+ * patterns, biome's `"recommended": false` and `"enabled": false`, and other
32
+ * linters' files.
33
+ */
34
+ import { execFile } from "node:child_process";
35
+ import { SANCTION_KEY } from "../weakened-tests.js";
36
+ export const name = "RT-92";
37
+ const LINT_FILES = new Set([
38
+ ".eslintrc",
39
+ ".eslintrc.json",
40
+ ".eslintrc.yml",
41
+ ".eslintrc.yaml",
42
+ ".eslintrc.js",
43
+ ".eslintrc.cjs",
44
+ "eslint.config.js",
45
+ "eslint.config.mjs",
46
+ "eslint.config.cjs",
47
+ "eslint.config.ts",
48
+ "eslint.config.mts",
49
+ "eslint.config.cts",
50
+ "biome.json",
51
+ "biome.jsonc",
52
+ ".oxlintrc.json",
53
+ ]);
54
+ const OFF_PAIR = new RegExp(String.raw `(?:"([^"]*)"|'([^']*)'|([A-Za-z@_$][A-Za-z0-9@/\-_.$]*))\s*:\s*(?:\[\s*)?(?:"off"|'off'|off|0)(?![A-Za-z0-9_.])`, "g");
55
+ function runGit(cwd, args) {
56
+ return new Promise((resolve, reject) => {
57
+ execFile("git", args, { cwd, maxBuffer: 1024 * 1024 * 1024, encoding: "buffer" }, (error, stdout, stderr) => {
58
+ if (error)
59
+ reject(new Error(stderr.toString("utf8").trim() || error.message));
60
+ else
61
+ resolve(stdout.toString("utf8"));
62
+ });
63
+ });
64
+ }
65
+ /** Escape everything outside printable ASCII, so evidence cannot replay a control sequence. */
66
+ function escapeText(text) {
67
+ let escaped = "";
68
+ for (const char of text) {
69
+ const codePoint = char.codePointAt(0) ?? 0;
70
+ escaped += codePoint >= 0x20 && codePoint <= 0x7e ? char : `\\u{${codePoint.toString(16).padStart(4, "0")}}`;
71
+ }
72
+ return escaped;
73
+ }
74
+ async function resolveCommit(cwd, revision) {
75
+ const fail = (cause) => new Error(`revision "${escapeText(String(revision))}" does not name a commit${cause}`);
76
+ // A revision that begins with a dash would be read as an option.
77
+ if (typeof revision !== "string" || revision.length === 0 || revision.startsWith("-"))
78
+ throw fail("");
79
+ let hash = "";
80
+ try {
81
+ hash = (await runGit(cwd, ["rev-parse", "--verify", "--quiet", `${revision}^{commit}`])).trim();
82
+ }
83
+ catch (error) {
84
+ throw fail(`: ${error.message}`);
85
+ }
86
+ if (hash.length === 0)
87
+ throw fail("");
88
+ return hash;
89
+ }
90
+ const isLintPath = (path) => LINT_FILES.has(path.slice(path.lastIndexOf("/") + 1));
91
+ const isNull = (sha) => /^0+$/.test(sha);
92
+ /** A blob read whole through git; an absent blob is an empty file. */
93
+ const readBlob = async (cwd, sha) => isNull(sha) ? "" : runGit(cwd, ["cat-file", "blob", sha]);
94
+ /** How many off pairs each rule name has in a file's text. */
95
+ function countOffPairs(content) {
96
+ const counts = new Map();
97
+ for (const match of content.matchAll(OFF_PAIR)) {
98
+ const rule = match[1] ?? match[2] ?? match[3] ?? "";
99
+ counts.set(rule, (counts.get(rule) ?? 0) + 1);
100
+ }
101
+ return counts;
102
+ }
103
+ async function compare(cwd, from, to) {
104
+ const out = await runGit(cwd, ["diff", "--raw", "-z", "-M", "--no-abbrev", "--no-ext-diff", "--no-relative", from, to, "--"]);
105
+ const fields = out.split("\0");
106
+ const drafts = [];
107
+ for (let i = 0; i < fields.length; i++) {
108
+ const head = fields[i] ?? "";
109
+ if (!head.startsWith(":"))
110
+ continue;
111
+ const [, , oldSha = "", newSha = "", status = ""] = head.slice(1).split(" ");
112
+ const renamed = status.startsWith("R") || status.startsWith("C");
113
+ i++;
114
+ const newPath = renamed ? (fields[++i] ?? "") : (fields[i] ?? "");
115
+ const letter = status.charAt(0);
116
+ if (!["A", "M", "R", "C", "T"].includes(letter) || !isLintPath(newPath))
117
+ continue;
118
+ const before = countOffPairs(letter === "A" ? "" : await readBlob(cwd, oldSha));
119
+ const after = countOffPairs(await readBlob(cwd, newSha));
120
+ for (const [rule, count] of after) {
121
+ if (count > (before.get(rule) ?? 0))
122
+ drafts.push({ file: newPath, rule });
123
+ }
124
+ }
125
+ return drafts;
126
+ }
127
+ /** The text after the sanction key on every such line of the range's commit messages. */
128
+ async function readSanctionLines(cwd, from, to) {
129
+ const out = await runGit(cwd, ["log", "--format=%B%x00", `${from}..${to}`, "--"]);
130
+ const prefix = `${SANCTION_KEY}:`;
131
+ return out
132
+ .split(/[\n\0]/)
133
+ .map((line) => line.trim())
134
+ .filter((line) => line.startsWith(prefix))
135
+ .map((line) => line.slice(prefix.length).trim());
136
+ }
137
+ function sanctionFor(id, lines, given) {
138
+ const reason = Object.hasOwn(given, id) ? given[id] : undefined;
139
+ if (typeof reason === "string" && reason.trim().length > 0)
140
+ return reason.trim();
141
+ for (const line of lines) {
142
+ const rest = line.slice(id.length);
143
+ if (line.startsWith(id) && /^\s+\S/.test(rest))
144
+ return rest.trim();
145
+ }
146
+ return null;
147
+ }
148
+ /**
149
+ * Report each rule `base..candidate` turns off in a lint configuration file.
150
+ * Fails while any finding is unsanctioned, and fails closed: a revision that
151
+ * names no commit, or any git invocation that fails, is a `fail` record naming
152
+ * the cause - never a `pass`, never a throw.
153
+ */
154
+ export async function check(options) {
155
+ try {
156
+ const { cwd, base, candidate } = options;
157
+ const baseCommit = await resolveCommit(cwd, base);
158
+ const candidateCommit = await resolveCommit(cwd, candidate);
159
+ let ancestor;
160
+ try {
161
+ ancestor = (await runGit(cwd, ["merge-base", baseCommit, candidateCommit])).trim();
162
+ }
163
+ catch (error) {
164
+ throw new Error(`no merge base of "${escapeText(base)}" and "${escapeText(candidate)}": ${error.message}`);
165
+ }
166
+ const drafts = await compare(cwd, ancestor, candidateCommit);
167
+ if (drafts.length === 0)
168
+ return { name, status: "pass", evidence: ["no lint rule turned off"] };
169
+ const lines = await readSanctionLines(cwd, ancestor, candidateCommit);
170
+ let open = false;
171
+ const evidence = drafts.map((draft) => {
172
+ const reason = sanctionFor(`rule-off:${draft.file}`, lines, options.sanctions ?? {});
173
+ if (reason === null)
174
+ open = true;
175
+ const tail = reason === null ? "" : ` - sanctioned: ${escapeText(reason)}`;
176
+ return `${escapeText(draft.file)}: rule-off ${escapeText(draft.rule)}${tail}`;
177
+ });
178
+ return { name, status: open ? "fail" : "pass", evidence };
179
+ }
180
+ catch (error) {
181
+ return { name, status: "fail", evidence: [escapeText(error instanceof Error ? error.message : String(error))] };
182
+ }
183
+ }
184
+ //# sourceMappingURL=lint-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint-rules.js","sourceRoot":"","sources":["../../src/checks/lint-rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Path policy.
3
+ *
4
+ * Which paths a change should not reach without a second look is the
5
+ * repository's to say, so this check holds no list: it reads `forbidden` and
6
+ * `allowed` from its options and reports each path the range adds, changes,
7
+ * deletes or renames - both ends of a rename - that a `forbidden` pattern
8
+ * matches or, when `allowed` is given, that no `allowed` pattern matches.
9
+ *
10
+ * Paths come from git's list of the changes between the merge base and the
11
+ * candidate, never from the working tree. A pattern is matched against a whole
12
+ * path: `?` is one character but `/`, `*` is any run but `/`, a segment that
13
+ * is exactly `**` is any number of whole segments (none included), and every
14
+ * other character - case included - matches only itself.
15
+ */
16
+ import type { CheckRecord } from "../index.js";
17
+ export declare const name = "RT-57";
18
+ /** The options a configuration file may set for this check. */
19
+ export declare const optionKinds: {
20
+ forbidden: string;
21
+ allowed: string;
22
+ };
23
+ export interface PathPolicyOptions {
24
+ readonly cwd: string;
25
+ readonly base: string;
26
+ readonly candidate: string;
27
+ /** Ids (`<category>:<path>`) mapped to reasons. */
28
+ readonly sanctions?: Readonly<Record<string, string>> | undefined;
29
+ readonly forbidden?: readonly string[] | undefined;
30
+ readonly allowed?: readonly string[] | undefined;
31
+ }
32
+ /** Whether one pattern matches one whole path. */
33
+ export declare function matchesPattern(pattern: string, path: string): boolean;
34
+ /**
35
+ * Report each path `base..candidate` touches that the policy forbids or does
36
+ * not allow. Fails while any finding is unsanctioned, and fails closed: a bad
37
+ * revision, a bad option or a failing git invocation is a `fail` record naming
38
+ * the cause - never a `pass`, never a throw.
39
+ */
40
+ export declare function check(options: PathPolicyOptions): Promise<CheckRecord>;