@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.
- package/LICENSE +21 -0
- package/README.md +273 -2
- package/dist/action.d.ts +52 -0
- package/dist/action.js +182 -0
- package/dist/action.js.map +1 -0
- package/dist/checks/action-pins.d.ts +52 -0
- package/dist/checks/action-pins.js +196 -0
- package/dist/checks/action-pins.js.map +1 -0
- package/dist/checks/autosquash.d.ts +23 -0
- package/dist/checks/autosquash.js +85 -0
- package/dist/checks/autosquash.js.map +1 -0
- package/dist/checks/bidi-controls.d.ts +58 -0
- package/dist/checks/bidi-controls.js +172 -0
- package/dist/checks/bidi-controls.js.map +1 -0
- package/dist/checks/binary-files.d.ts +53 -0
- package/dist/checks/binary-files.js +157 -0
- package/dist/checks/binary-files.js.map +1 -0
- package/dist/checks/config-changes.d.ts +57 -0
- package/dist/checks/config-changes.js +175 -0
- package/dist/checks/config-changes.js.map +1 -0
- package/dist/checks/credentials.d.ts +35 -0
- package/dist/checks/credentials.js +138 -0
- package/dist/checks/credentials.js.map +1 -0
- package/dist/checks/install-scripts.d.ts +39 -0
- package/dist/checks/install-scripts.js +173 -0
- package/dist/checks/install-scripts.js.map +1 -0
- package/dist/checks/lint-rules.d.ts +49 -0
- package/dist/checks/lint-rules.js +184 -0
- package/dist/checks/lint-rules.js.map +1 -0
- package/dist/checks/path-policy.d.ts +40 -0
- package/dist/checks/path-policy.js +195 -0
- package/dist/checks/path-policy.js.map +1 -0
- package/dist/checks/raised-timeouts.d.ts +66 -0
- package/dist/checks/raised-timeouts.js +248 -0
- package/dist/checks/raised-timeouts.js.map +1 -0
- package/dist/checks/script-changes.d.ts +51 -0
- package/dist/checks/script-changes.js +199 -0
- package/dist/checks/script-changes.js.map +1 -0
- package/dist/checks/secret-files.d.ts +58 -0
- package/dist/checks/secret-files.js +165 -0
- package/dist/checks/secret-files.js.map +1 -0
- package/dist/checks/strictness.d.ts +59 -0
- package/dist/checks/strictness.js +260 -0
- package/dist/checks/strictness.js.map +1 -0
- package/dist/checks/submodules.d.ts +51 -0
- package/dist/checks/submodules.js +172 -0
- package/dist/checks/submodules.js.map +1 -0
- package/dist/checks/suppressions.d.ts +53 -0
- package/dist/checks/suppressions.js +203 -0
- package/dist/checks/suppressions.js.map +1 -0
- package/dist/checks/workflow-permissions.d.ts +56 -0
- package/dist/checks/workflow-permissions.js +237 -0
- package/dist/checks/workflow-permissions.js.map +1 -0
- package/dist/checks/workflow-weakening.d.ts +38 -0
- package/dist/checks/workflow-weakening.js +172 -0
- package/dist/checks/workflow-weakening.js.map +1 -0
- package/dist/cli.d.ts +31 -0
- package/dist/cli.js +300 -0
- package/dist/cli.js.map +1 -0
- package/dist/commit-range.d.ts +28 -0
- package/dist/commit-range.js +152 -0
- package/dist/commit-range.js.map +1 -0
- package/dist/config.d.ts +35 -0
- package/dist/config.js +235 -0
- package/dist/config.js.map +1 -0
- package/dist/conflict-markers.d.ts +40 -0
- package/dist/conflict-markers.js +234 -0
- package/dist/conflict-markers.js.map +1 -0
- package/dist/dependency-sources.d.ts +32 -0
- package/dist/dependency-sources.js +260 -0
- package/dist/dependency-sources.js.map +1 -0
- package/dist/figure-claims.d.ts +35 -0
- package/dist/figure-claims.js +139 -0
- package/dist/figure-claims.js.map +1 -0
- package/dist/figure-text.d.ts +22 -0
- package/dist/figure-text.js +119 -0
- package/dist/figure-text.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/large-blobs.d.ts +35 -0
- package/dist/large-blobs.js +163 -0
- package/dist/large-blobs.js.map +1 -0
- package/dist/lockfile-drift.d.ts +36 -0
- package/dist/lockfile-drift.js +206 -0
- package/dist/lockfile-drift.js.map +1 -0
- package/dist/lowered-thresholds.d.ts +70 -0
- package/dist/lowered-thresholds.js +281 -0
- package/dist/lowered-thresholds.js.map +1 -0
- package/dist/measurement-rule.d.ts +39 -0
- package/dist/measurement-rule.js +213 -0
- package/dist/measurement-rule.js.map +1 -0
- package/dist/portable-paths.d.ts +37 -0
- package/dist/portable-paths.js +215 -0
- package/dist/portable-paths.js.map +1 -0
- package/dist/registry.d.ts +43 -0
- package/dist/registry.js +144 -0
- package/dist/registry.js.map +1 -0
- package/dist/replay-plan.d.ts +36 -0
- package/dist/replay-plan.js +196 -0
- package/dist/replay-plan.js.map +1 -0
- package/dist/replayed-figures.d.ts +36 -0
- package/dist/replayed-figures.js +159 -0
- package/dist/replayed-figures.js.map +1 -0
- package/dist/snapshot-refreshes.d.ts +36 -0
- package/dist/snapshot-refreshes.js +202 -0
- package/dist/snapshot-refreshes.js.map +1 -0
- package/dist/stray-files.d.ts +52 -0
- package/dist/stray-files.js +234 -0
- package/dist/stray-files.js.map +1 -0
- package/dist/symlinks.d.ts +28 -0
- package/dist/symlinks.js +193 -0
- package/dist/symlinks.js.map +1 -0
- package/dist/trailers.d.ts +36 -0
- package/dist/trailers.js +110 -0
- package/dist/trailers.js.map +1 -0
- package/dist/weakened-tests.d.ts +94 -0
- package/dist/weakened-tests.js +458 -0
- package/dist/weakened-tests.js.map +1 -0
- package/package.json +31 -4
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot refreshes.
|
|
3
|
+
*
|
|
4
|
+
* A snapshot test passes when the output matches what was recorded, so the
|
|
5
|
+
* cheapest way to make a failing one pass is to record again. This check
|
|
6
|
+
* reports every snapshot `base..candidate` rewrites or deletes, and says
|
|
7
|
+
* whether a commit that did it also changed a file that is neither a snapshot
|
|
8
|
+
* nor a test. A new snapshot, or one only renamed with its content unchanged,
|
|
9
|
+
* is not a finding. Inline snapshots are not this version's.
|
|
10
|
+
*
|
|
11
|
+
* Only the range's changes are read, through git with an argument vector: the
|
|
12
|
+
* diff from the merge base of the two revisions to the candidate, with renames
|
|
13
|
+
* detected, and the paths each commit of `base..candidate` changed. A snapshot
|
|
14
|
+
* the base changed before the range is not reported.
|
|
15
|
+
*/
|
|
16
|
+
import { execFile } from "node:child_process";
|
|
17
|
+
import { SANCTION_KEY, WEAKENING_SPELLINGS } from "./weakened-tests.js";
|
|
18
|
+
const NAME = "RT-16";
|
|
19
|
+
/**
|
|
20
|
+
* The shapes of a snapshot path, each tested against a path relative to the
|
|
21
|
+
* repository root. The check reads this list each time it runs, so a consumer
|
|
22
|
+
* appends its own shape.
|
|
23
|
+
*/
|
|
24
|
+
export const SNAPSHOT_PATHS = [/(?:^|\/)__snapshots__\//, /\.snap$/, /\.ambr$/];
|
|
25
|
+
function runGit(cwd, args) {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
execFile("git", args, { cwd, maxBuffer: 1024 * 1024 * 1024, encoding: "utf8" }, (error, stdout, stderr) => {
|
|
28
|
+
if (error) {
|
|
29
|
+
reject(new Error(stderr.trim() || error.message));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
resolve(stdout);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** Escape everything outside printable ASCII, so evidence cannot replay a control sequence. */
|
|
37
|
+
function escapeText(text) {
|
|
38
|
+
let escaped = "";
|
|
39
|
+
for (const char of text) {
|
|
40
|
+
const codePoint = char.codePointAt(0) ?? 0;
|
|
41
|
+
escaped += codePoint >= 0x20 && codePoint <= 0x7e ? char : `\\u{${codePoint.toString(16).padStart(4, "0")}}`;
|
|
42
|
+
}
|
|
43
|
+
return escaped;
|
|
44
|
+
}
|
|
45
|
+
function quote(text) {
|
|
46
|
+
return `"${escapeText(text)}"`;
|
|
47
|
+
}
|
|
48
|
+
async function resolveCommit(cwd, revision) {
|
|
49
|
+
// A revision that begins with a dash would be read as an option.
|
|
50
|
+
if (typeof revision !== "string" || revision.length === 0 || revision.startsWith("-")) {
|
|
51
|
+
throw new Error(`revision ${quote(String(revision))} does not name a commit`);
|
|
52
|
+
}
|
|
53
|
+
let hash = "";
|
|
54
|
+
try {
|
|
55
|
+
hash = (await runGit(cwd, ["rev-parse", "--verify", "--quiet", `${revision}^{commit}`])).trim();
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
throw new Error(`revision ${quote(revision)} does not name a commit: ${error.message}`);
|
|
59
|
+
}
|
|
60
|
+
if (hash.length === 0) {
|
|
61
|
+
throw new Error(`revision ${quote(revision)} does not name a commit`);
|
|
62
|
+
}
|
|
63
|
+
return hash;
|
|
64
|
+
}
|
|
65
|
+
function parseRaw(out) {
|
|
66
|
+
const tokens = out.split("\0");
|
|
67
|
+
const entries = [];
|
|
68
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
69
|
+
const meta = tokens[i] ?? "";
|
|
70
|
+
if (!meta.startsWith(":")) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const fields = meta.slice(1).split(" ");
|
|
74
|
+
const status = (fields[4] ?? "").charAt(0);
|
|
75
|
+
const oldPath = tokens[i + 1] ?? "";
|
|
76
|
+
let newPath = oldPath;
|
|
77
|
+
if (status === "R" || status === "C") {
|
|
78
|
+
newPath = tokens[i + 2] ?? "";
|
|
79
|
+
i += 2;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
i += 1;
|
|
83
|
+
}
|
|
84
|
+
entries.push({ status, contentChanged: (fields[2] ?? "") !== (fields[3] ?? ""), oldPath, newPath });
|
|
85
|
+
}
|
|
86
|
+
return entries;
|
|
87
|
+
}
|
|
88
|
+
function matchesAny(patterns, path) {
|
|
89
|
+
return patterns.some((pattern) => {
|
|
90
|
+
pattern.lastIndex = 0;
|
|
91
|
+
const hit = pattern.test(path);
|
|
92
|
+
pattern.lastIndex = 0;
|
|
93
|
+
return hit;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function draftsOf(entries, isSnapshot) {
|
|
97
|
+
const drafts = [];
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
const { status, oldPath, newPath } = entry;
|
|
100
|
+
if (status === "A" || status === "C" || !isSnapshot(oldPath)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (status === "R" && !entry.contentChanged) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (status === "D" || !isSnapshot(newPath)) {
|
|
107
|
+
drafts.push({ category: "snapshot-deleted", path: oldPath, paths: [oldPath, newPath] });
|
|
108
|
+
}
|
|
109
|
+
else if (entry.contentChanged) {
|
|
110
|
+
drafts.push({ category: "snapshot-refreshed", path: newPath, paths: [oldPath, newPath] });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return drafts;
|
|
114
|
+
}
|
|
115
|
+
/** The commits of the range, oldest first, with what each changed. */
|
|
116
|
+
async function readCommits(cwd, from, to) {
|
|
117
|
+
const ids = (await runGit(cwd, ["rev-list", "--reverse", `${from}..${to}`, "--"])).split("\n").filter(Boolean);
|
|
118
|
+
const commits = [];
|
|
119
|
+
for (const id of ids) {
|
|
120
|
+
const raw = await runGit(cwd, ["diff-tree", "--root", "-r", "-M", "-z", "--raw", "--no-commit-id", id, "--"]);
|
|
121
|
+
commits.push({ id, entries: parseRaw(raw) });
|
|
122
|
+
}
|
|
123
|
+
return commits;
|
|
124
|
+
}
|
|
125
|
+
async function readSanctionLines(cwd, from, to) {
|
|
126
|
+
const out = await runGit(cwd, ["log", "--format=%B%x00", `${from}..${to}`, "--"]);
|
|
127
|
+
const prefix = `${SANCTION_KEY}:`;
|
|
128
|
+
return out
|
|
129
|
+
.split(/[\n\0]/)
|
|
130
|
+
.map((line) => line.trim())
|
|
131
|
+
.filter((line) => line.startsWith(prefix))
|
|
132
|
+
.map((line) => line.slice(prefix.length).trim());
|
|
133
|
+
}
|
|
134
|
+
function sanctionFor(id, lines, given) {
|
|
135
|
+
const reason = Object.hasOwn(given, id) ? given[id] : undefined;
|
|
136
|
+
if (typeof reason === "string" && reason.trim().length > 0) {
|
|
137
|
+
return reason.trim();
|
|
138
|
+
}
|
|
139
|
+
for (const line of lines) {
|
|
140
|
+
const rest = line.slice(id.length);
|
|
141
|
+
if (line.startsWith(id) && /^\s+\S/.test(rest)) {
|
|
142
|
+
return rest.trim();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Report the snapshots `base..candidate` rewrites or deletes. Fails while any
|
|
149
|
+
* finding is unsanctioned, and fails closed: a revision that names no commit,
|
|
150
|
+
* or any git invocation that fails, is a `fail` record naming the cause.
|
|
151
|
+
*/
|
|
152
|
+
export async function checkSnapshotRefreshes(options) {
|
|
153
|
+
try {
|
|
154
|
+
const { cwd, base, candidate } = options;
|
|
155
|
+
const baseCommit = await resolveCommit(cwd, base);
|
|
156
|
+
const candidateCommit = await resolveCommit(cwd, candidate);
|
|
157
|
+
let ancestor;
|
|
158
|
+
try {
|
|
159
|
+
ancestor = (await runGit(cwd, ["merge-base", baseCommit, candidateCommit])).trim();
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
throw new Error(`no merge base of ${quote(base)} and ${quote(candidate)}: ${error.message}`);
|
|
163
|
+
}
|
|
164
|
+
const snapshotPatterns = [...SNAPSHOT_PATHS];
|
|
165
|
+
const testPatterns = [...WEAKENING_SPELLINGS.testFiles];
|
|
166
|
+
const isSnapshot = (path) => matchesAny(snapshotPatterns, path);
|
|
167
|
+
const isCode = (path) => !isSnapshot(path) && !matchesAny(testPatterns, path);
|
|
168
|
+
const raw = await runGit(cwd, ["diff", "-z", "--raw", "-M", "--no-ext-diff", ancestor, candidateCommit, "--"]);
|
|
169
|
+
const drafts = draftsOf(parseRaw(raw), isSnapshot);
|
|
170
|
+
if (drafts.length === 0) {
|
|
171
|
+
return { name: NAME, status: "pass", evidence: ["no snapshot refreshed or deleted"] };
|
|
172
|
+
}
|
|
173
|
+
const commits = await readCommits(cwd, baseCommit, candidateCommit);
|
|
174
|
+
const lines = await readSanctionLines(cwd, baseCommit, candidateCommit);
|
|
175
|
+
const given = options.sanctions ?? {};
|
|
176
|
+
let open = 0;
|
|
177
|
+
const evidence = drafts.map((draft) => {
|
|
178
|
+
const id = `${draft.category}:${draft.path}`;
|
|
179
|
+
const beside = commits
|
|
180
|
+
.filter((commit) => commit.entries.some((entry) => entry.status !== "A" &&
|
|
181
|
+
(draft.paths.includes(entry.oldPath) || draft.paths.includes(entry.newPath)) &&
|
|
182
|
+
(entry.status === "D" || entry.contentChanged)) && commit.entries.some((entry) => isCode(entry.oldPath) || isCode(entry.newPath)))
|
|
183
|
+
.map((commit) => commit.id);
|
|
184
|
+
const word = draft.category === "snapshot-deleted" ? "deleted" : "refreshed";
|
|
185
|
+
let line = `${escapeText(draft.path)}: ${word}`;
|
|
186
|
+
if (beside.length > 0) {
|
|
187
|
+
line += ` beside a code change in ${beside.join(", ")}`;
|
|
188
|
+
}
|
|
189
|
+
const reason = sanctionFor(id, lines, given);
|
|
190
|
+
if (reason === null) {
|
|
191
|
+
open += 1;
|
|
192
|
+
return `${line} - awaiting a sanction`;
|
|
193
|
+
}
|
|
194
|
+
return `${line} - sanctioned: ${escapeText(reason)}`;
|
|
195
|
+
});
|
|
196
|
+
return { name: NAME, status: open > 0 ? "fail" : "pass", evidence };
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
return { name: NAME, status: "fail", evidence: [escapeText(error instanceof Error ? error.message : String(error))] };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=snapshot-refreshes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-refreshes.js","sourceRoot":"","sources":["../src/snapshot-refreshes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stray files.
|
|
3
|
+
*
|
|
4
|
+
* A range that adds a scratch script, a debug log, an editor backup, a
|
|
5
|
+
* coverage directory, a build artefact or a copied-aside file added it because
|
|
6
|
+
* an agent was working, not because anyone decided to keep it. This check reads
|
|
7
|
+
* the paths `base..candidate` ADDS and reports two separate things:
|
|
8
|
+
*
|
|
9
|
+
* - a path whose name matches a shape in `STRAY_SHAPES` - the fix is to delete
|
|
10
|
+
* the file;
|
|
11
|
+
* - a path the repository's own ignore rules would exclude, though it is
|
|
12
|
+
* tracked - the fix is either to delete the file or to correct the rules,
|
|
13
|
+
* because the tree and the rules disagree.
|
|
14
|
+
*
|
|
15
|
+
* Only ADDED paths count. A modification, or a rename of a file, is not an
|
|
16
|
+
* addition, so an ordinary rename of a legitimately-named file is never a
|
|
17
|
+
* finding. The paths come from git's diff of the two commits, never from the
|
|
18
|
+
* working tree, so a file a developer has locally is not a finding against a
|
|
19
|
+
* candidate that does not contain it.
|
|
20
|
+
*/
|
|
21
|
+
import type { CheckRecord } from "./index.js";
|
|
22
|
+
/** Where to look, and which two revisions bound the range. */
|
|
23
|
+
export interface StrayFilesOptions {
|
|
24
|
+
/** The git repository to run in. */
|
|
25
|
+
readonly cwd: string;
|
|
26
|
+
/** The revision the range starts after. */
|
|
27
|
+
readonly base: string;
|
|
28
|
+
/** The revision the range ends at. */
|
|
29
|
+
readonly candidate: string;
|
|
30
|
+
}
|
|
31
|
+
/** One stated stray shape. */
|
|
32
|
+
export interface StrayShape {
|
|
33
|
+
/** Short stable name; it is what a finding prints. */
|
|
34
|
+
readonly name: string;
|
|
35
|
+
/** Why a path of this shape is probably not meant to be kept. */
|
|
36
|
+
readonly description: string;
|
|
37
|
+
/** Tested against the added path, relative to the repository root, with `/` separators. */
|
|
38
|
+
readonly pattern: RegExp;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The stray shapes. The list is read each time the check runs, so a consumer
|
|
42
|
+
* with its own conventions appends an entry (`STRAY_SHAPES.push({...})`)
|
|
43
|
+
* instead of forking the matcher. Order is only the order findings print in.
|
|
44
|
+
*/
|
|
45
|
+
export declare const STRAY_SHAPES: StrayShape[];
|
|
46
|
+
/**
|
|
47
|
+
* Check the paths `base..candidate` adds for stray shapes and for paths the
|
|
48
|
+
* ignore rules would exclude. Fails closed: a revision that names no commit,
|
|
49
|
+
* or any git invocation that fails, is a `fail` record naming the cause -
|
|
50
|
+
* never a `pass`, and never a thrown error.
|
|
51
|
+
*/
|
|
52
|
+
export declare function checkStrayFiles(options: StrayFilesOptions): Promise<CheckRecord>;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stray files.
|
|
3
|
+
*
|
|
4
|
+
* A range that adds a scratch script, a debug log, an editor backup, a
|
|
5
|
+
* coverage directory, a build artefact or a copied-aside file added it because
|
|
6
|
+
* an agent was working, not because anyone decided to keep it. This check reads
|
|
7
|
+
* the paths `base..candidate` ADDS and reports two separate things:
|
|
8
|
+
*
|
|
9
|
+
* - a path whose name matches a shape in `STRAY_SHAPES` - the fix is to delete
|
|
10
|
+
* the file;
|
|
11
|
+
* - a path the repository's own ignore rules would exclude, though it is
|
|
12
|
+
* tracked - the fix is either to delete the file or to correct the rules,
|
|
13
|
+
* because the tree and the rules disagree.
|
|
14
|
+
*
|
|
15
|
+
* Only ADDED paths count. A modification, or a rename of a file, is not an
|
|
16
|
+
* addition, so an ordinary rename of a legitimately-named file is never a
|
|
17
|
+
* finding. The paths come from git's diff of the two commits, never from the
|
|
18
|
+
* working tree, so a file a developer has locally is not a finding against a
|
|
19
|
+
* candidate that does not contain it.
|
|
20
|
+
*/
|
|
21
|
+
import { execFile } from "node:child_process";
|
|
22
|
+
const NAME = "RT-05";
|
|
23
|
+
/**
|
|
24
|
+
* The stray shapes. The list is read each time the check runs, so a consumer
|
|
25
|
+
* with its own conventions appends an entry (`STRAY_SHAPES.push({...})`)
|
|
26
|
+
* instead of forking the matcher. Order is only the order findings print in.
|
|
27
|
+
*/
|
|
28
|
+
export const STRAY_SHAPES = [
|
|
29
|
+
{
|
|
30
|
+
name: "log-file",
|
|
31
|
+
description: "a .log file is output of a run, not source",
|
|
32
|
+
pattern: /\.log$/i,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "editor-backup",
|
|
36
|
+
description: "an editor or tool backup (.bak, .orig, .swp, .swo, trailing ~)",
|
|
37
|
+
pattern: /(?:\.(?:bak|orig|swp|swo)|~)$/i,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "set-aside-copy",
|
|
41
|
+
description: "a copy set aside beside the original, such as 'index 2.ts' or 'index copy.ts'",
|
|
42
|
+
pattern: /(?:^|\/)[^/]*[^/ ] (?:\d+|copy(?: \d+)?|\(\d+\))(?:\.[^/ ]*)?$/i,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "scratch-script",
|
|
46
|
+
description: "a scratch or temporary script (scratch.*, tmp.*, debug.*)",
|
|
47
|
+
pattern: /(?:^|\/)(?:scratch|tmp|temp|debug)(?:[-_.][^/]*)?\.(?:[cm]?[jt]sx?|py|sh|rb)$/i,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "coverage-output",
|
|
51
|
+
description: "anything under coverage/ is generated by a test run",
|
|
52
|
+
pattern: /(?:^|\/)coverage\//,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "build-output",
|
|
56
|
+
description: "anything under dist/ or build/ is a build artefact",
|
|
57
|
+
pattern: /(?:^|\/)(?:dist|build)\//,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "os-metadata",
|
|
61
|
+
description: "operating-system folder metadata (.DS_Store, Thumbs.db)",
|
|
62
|
+
pattern: /(?:^|\/)(?:\.DS_Store|Thumbs\.db)$/,
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
function runGit(cwd, args, input, okCodes = [0]) {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const child = execFile("git", args, { cwd, maxBuffer: 1024 * 1024 * 1024, encoding: "buffer" }, (error, stdout, stderr) => {
|
|
68
|
+
if (error) {
|
|
69
|
+
const code = error.code;
|
|
70
|
+
if (typeof code === "number" && okCodes.includes(code)) {
|
|
71
|
+
resolve(stdout);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const detail = stderr.toString("utf8").trim();
|
|
75
|
+
reject(new Error(detail.length > 0 ? detail : error.message));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
resolve(stdout);
|
|
79
|
+
});
|
|
80
|
+
if (input !== undefined) {
|
|
81
|
+
child.stdin?.on("error", () => undefined);
|
|
82
|
+
child.stdin?.end(input);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/** Escape everything outside printable ASCII so a name cannot replay a control sequence. */
|
|
87
|
+
function escapeText(text) {
|
|
88
|
+
let escaped = "";
|
|
89
|
+
for (const char of text) {
|
|
90
|
+
const codePoint = char.codePointAt(0) ?? 0;
|
|
91
|
+
if (codePoint >= 0x20 && codePoint <= 0x7e) {
|
|
92
|
+
escaped += char;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
escaped += `\\u{${codePoint.toString(16).padStart(4, "0")}}`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return escaped;
|
|
99
|
+
}
|
|
100
|
+
function quote(text) {
|
|
101
|
+
return `"${escapeText(text)}"`;
|
|
102
|
+
}
|
|
103
|
+
function decodePath(bytes) {
|
|
104
|
+
try {
|
|
105
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return bytes.toString("latin1");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function splitNul(out) {
|
|
112
|
+
const paths = [];
|
|
113
|
+
let start = 0;
|
|
114
|
+
for (let i = 0; i < out.length; i += 1) {
|
|
115
|
+
if (out[i] === 0) {
|
|
116
|
+
if (i > start) {
|
|
117
|
+
paths.push(decodePath(out.subarray(start, i)));
|
|
118
|
+
}
|
|
119
|
+
start = i + 1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return paths;
|
|
123
|
+
}
|
|
124
|
+
async function resolveCommit(cwd, revision) {
|
|
125
|
+
if (revision.length === 0 || revision.startsWith("-")) {
|
|
126
|
+
throw new Error(`revision ${quote(revision)} names no commit`);
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const out = await runGit(cwd, ["rev-parse", "--verify", "--quiet", `${revision}^{commit}`]);
|
|
130
|
+
const hash = out.toString("utf8").trim();
|
|
131
|
+
if (hash.length === 0) {
|
|
132
|
+
throw new Error("empty answer");
|
|
133
|
+
}
|
|
134
|
+
return hash;
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
throw new Error(`revision ${quote(revision)} names no commit: ${error.message}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** The paths the range adds: status A only, with renames detected so a rename is not one. */
|
|
141
|
+
async function addedPaths(root, base, candidate) {
|
|
142
|
+
const label = `${base.slice(0, 12)} and ${candidate.slice(0, 12)}`;
|
|
143
|
+
let ancestor;
|
|
144
|
+
try {
|
|
145
|
+
ancestor = (await runGit(root, ["merge-base", base, candidate])).toString("utf8").trim();
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
throw new Error(`no merge base of ${label}: ${error.message}`);
|
|
149
|
+
}
|
|
150
|
+
if (ancestor.length === 0) {
|
|
151
|
+
throw new Error(`no merge base of ${label}`);
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const out = await runGit(root, [
|
|
155
|
+
"-c",
|
|
156
|
+
"core.quotePath=false",
|
|
157
|
+
"diff",
|
|
158
|
+
"--no-ext-diff",
|
|
159
|
+
"--no-textconv",
|
|
160
|
+
"--no-color",
|
|
161
|
+
"-M",
|
|
162
|
+
"-l0",
|
|
163
|
+
"--diff-filter=A",
|
|
164
|
+
"--name-only",
|
|
165
|
+
"-z",
|
|
166
|
+
ancestor,
|
|
167
|
+
candidate,
|
|
168
|
+
"--",
|
|
169
|
+
]);
|
|
170
|
+
return splitNul(out);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
throw new Error(`could not read the paths added by the range: ${error.message}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/** Which of the paths the repository's ignore rules would exclude, tracked or not. */
|
|
177
|
+
async function ignoredPaths(root, paths) {
|
|
178
|
+
if (paths.length === 0) {
|
|
179
|
+
return new Set();
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
const out = await runGit(root, ["check-ignore", "--no-index", "-z", "--stdin"], paths.map((path) => `${path}\0`).join(""), [0, 1]);
|
|
183
|
+
return new Set(splitNul(out));
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
throw new Error(`could not read the ignore rules: ${error.message}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Check the paths `base..candidate` adds for stray shapes and for paths the
|
|
191
|
+
* ignore rules would exclude. Fails closed: a revision that names no commit,
|
|
192
|
+
* or any git invocation that fails, is a `fail` record naming the cause -
|
|
193
|
+
* never a `pass`, and never a thrown error.
|
|
194
|
+
*/
|
|
195
|
+
export async function checkStrayFiles(options) {
|
|
196
|
+
try {
|
|
197
|
+
const { cwd, base, candidate } = options;
|
|
198
|
+
const baseCommit = await resolveCommit(cwd, base);
|
|
199
|
+
const candidateCommit = await resolveCommit(cwd, candidate);
|
|
200
|
+
const root = (await runGit(cwd, ["rev-parse", "--show-toplevel"])).toString("utf8").replace(/\r?\n$/, "");
|
|
201
|
+
const added = (await addedPaths(root, baseCommit, candidateCommit)).sort();
|
|
202
|
+
const shapes = [...STRAY_SHAPES];
|
|
203
|
+
const evidence = [];
|
|
204
|
+
for (const path of added) {
|
|
205
|
+
for (const shape of shapes) {
|
|
206
|
+
shape.pattern.lastIndex = 0;
|
|
207
|
+
if (shape.pattern.test(path)) {
|
|
208
|
+
evidence.push(`${quote(path)} matches stray shape ${quote(shape.name)}: ${shape.description}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const ignored = await ignoredPaths(root, added);
|
|
213
|
+
for (const path of added) {
|
|
214
|
+
if (ignored.has(path)) {
|
|
215
|
+
evidence.push(`${quote(path)} is added but the ignore rules exclude it: delete the file or correct the ignore rules`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (evidence.length > 0) {
|
|
219
|
+
return { name: NAME, status: "fail", evidence };
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
name: NAME,
|
|
223
|
+
status: "pass",
|
|
224
|
+
evidence: [
|
|
225
|
+
`${added.length} path(s) added in ${quote(base)}..${quote(candidate)} match no stray shape and no ignore rule`,
|
|
226
|
+
],
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
231
|
+
return { name: NAME, status: "fail", evidence: [escapeText(message)] };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=stray-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stray-files.js","sourceRoot":"","sources":["../src/stray-files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Symlinks.
|
|
3
|
+
*
|
|
4
|
+
* A symbolic link is committed as a blob holding its target, so a diff shows a
|
|
5
|
+
* link to `/etc/passwd` as one harmless line of text. This check reads every
|
|
6
|
+
* link `base..candidate` adds, retargets or turns a file into, takes the
|
|
7
|
+
* target from the candidate's own blob (never from the working tree), resolves
|
|
8
|
+
* it from the link's own directory and reports one that is [absolute], that
|
|
9
|
+
* climbs [outside] the repository, or that lands in the repository's [.git]
|
|
10
|
+
* directory. A target inside the repository is not a finding whether or not
|
|
11
|
+
* anything exists there.
|
|
12
|
+
*/
|
|
13
|
+
import type { CheckRecord } from "./index.js";
|
|
14
|
+
/** Where to look, and which two revisions bound the range. */
|
|
15
|
+
export interface SymlinksOptions {
|
|
16
|
+
/** The git repository to run in. */
|
|
17
|
+
readonly cwd: string;
|
|
18
|
+
/** The revision the range starts after. */
|
|
19
|
+
readonly base: string;
|
|
20
|
+
/** The revision the range ends at. */
|
|
21
|
+
readonly candidate: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check every link `base..candidate` adds or changes. Fails closed: a revision
|
|
25
|
+
* that names no commit, or any git invocation that fails, is a `fail` record
|
|
26
|
+
* naming the cause - never a `pass`, and never a thrown error.
|
|
27
|
+
*/
|
|
28
|
+
export declare function checkSymlinks(options: SymlinksOptions): Promise<CheckRecord>;
|