@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vladimir Trofimyuk
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,274 @@
|
|
|
1
|
-
#
|
|
1
|
+
# repo-truth
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
repo-truth reads a range of git commits - `base..candidate`, the change a branch or a
|
|
4
|
+
pull request makes - and reports the moves that make a repository look healthier than
|
|
5
|
+
it is: a test skipped, focused or deleted, a coverage minimum lowered, a timeout
|
|
6
|
+
raised, a workflow or a compiler setting loosened, a lint rule turned off, a
|
|
7
|
+
credential or a secret file committed, a dependency from outside the registry, an
|
|
8
|
+
action that is not pinned, a figure in the documentation with no command behind it.
|
|
9
|
+
It is one command, `repo-truth check`, and a GitHub Action that runs it over a pull
|
|
10
|
+
request.
|
|
11
|
+
|
|
12
|
+
Each check answers with a record: its name, a status - `pass`, `fail` or `skip` - and
|
|
13
|
+
evidence lines that name the file, the line or the commit. Each check reads the range
|
|
14
|
+
through git and fails closed: a git call that fails is a `fail` record, never a `pass`.
|
|
15
|
+
A revision that names no commit stops the command before any check runs, with exit 2.
|
|
16
|
+
The exit code says whether any check failed.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
Node.js 20 or newer, and git on the `PATH`.
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npm install --save-dev @trofimyukdev/repo-truth
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The package installs one command, `repo-truth`. In a project that installs it,
|
|
27
|
+
`npx repo-truth` runs it. Outside one, name the package: the unscoped name
|
|
28
|
+
`repo-truth` on npm is another project, so a bare `npx repo-truth` would fetch and
|
|
29
|
+
run that one.
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
npm exec --package=@trofimyukdev/repo-truth -- repo-truth check --base origin/main --candidate HEAD
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
repo-truth check --base <rev> --candidate <rev>
|
|
39
|
+
repo-truth check --target <rev> --candidate <rev>
|
|
40
|
+
repo-truth list
|
|
41
|
+
repo-truth --help
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`check` runs every registered check over `base..candidate` and prints one record per
|
|
45
|
+
check, in the order `repo-truth list` prints the names.
|
|
46
|
+
|
|
47
|
+
- `--base <rev>`: the range starts after this commit.
|
|
48
|
+
- `--target <rev>`, in place of `--base`: the branch a pull request merges into. The
|
|
49
|
+
range then starts at the merge base of the target and the candidate, so commits that
|
|
50
|
+
reached the target after the branch forked are not charged to it. A missing merge
|
|
51
|
+
base is a refusal; in a shallow clone the message asks for `fetch-depth: 0`. When
|
|
52
|
+
`GITHUB_EVENT_NAME` is set, it must be `pull_request` or `pull_request_target`.
|
|
53
|
+
- `--candidate <rev>`: the last commit of the range.
|
|
54
|
+
- `--check <name>`, repeatable: run only the named checks.
|
|
55
|
+
- `--except <name>`, repeatable: run every check but the named ones. It cannot be given
|
|
56
|
+
with `--check`, and it cannot name every check.
|
|
57
|
+
- `--format text|json`: `json` prints one document instead of the text:
|
|
58
|
+
`"schema": "repo-truth.check/v1"`, the resolved `base` and `candidate`, `config`,
|
|
59
|
+
`ok`, `exitCode` and the `records`.
|
|
60
|
+
- `--config <path>`: read the settings from this file instead of `repo-truth.json` at
|
|
61
|
+
the base commit (see Configuration).
|
|
62
|
+
- `--no-fetch`: skip the `git fetch --all --quiet` the command otherwise runs before it
|
|
63
|
+
resolves either revision.
|
|
64
|
+
|
|
65
|
+
The text form is one line per record, its status and its name, then its evidence lines
|
|
66
|
+
indented, and a last line, `repo-truth: all checks passed` or
|
|
67
|
+
`repo-truth: at least one check failed`. Text outside printable ASCII is printed as
|
|
68
|
+
`\u{hex}`.
|
|
69
|
+
|
|
70
|
+
The exit code is the contract. The header of `src/cli.ts` is the one place it is
|
|
71
|
+
stated; in short:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
0 every check that ran passed or skipped
|
|
75
|
+
1 at least one check failed
|
|
76
|
+
2 bad invocation or unrunnable environment - an unknown option or check,
|
|
77
|
+
--except with --check or naming every check, a revision that names no
|
|
78
|
+
commit, a failed fetch, a configuration the command refuses; never a
|
|
79
|
+
check's finding
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
On exit 2 nothing is written to standard output and no check has run. `repo-truth list`
|
|
83
|
+
answers 0 after printing the names, and 2 for any argument but `--help`.
|
|
84
|
+
|
|
85
|
+
## GitHub Action
|
|
86
|
+
|
|
87
|
+
The Action runs `repo-truth check` over the pull request that triggered it: `--target`
|
|
88
|
+
is the pull request's base branch on `origin`, `--candidate` its head commit. It needs
|
|
89
|
+
the whole history, so check out with `fetch-depth: 0`. It builds repo-truth from its
|
|
90
|
+
own source with `npm ci && npm run build`, and it writes the records to the job
|
|
91
|
+
summary.
|
|
92
|
+
|
|
93
|
+
```yaml
|
|
94
|
+
on: pull_request
|
|
95
|
+
|
|
96
|
+
jobs:
|
|
97
|
+
repo-truth:
|
|
98
|
+
runs-on: ubuntu-latest
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/checkout@v4
|
|
101
|
+
with:
|
|
102
|
+
fetch-depth: 0
|
|
103
|
+
- uses: trofimyukdev/repo-truth@v0.1.0
|
|
104
|
+
with:
|
|
105
|
+
except: RT-03
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
RT-03 fails a pull request whose head commit names no task in a `Task-Id` trailer.
|
|
109
|
+
The example leaves it out; a repository that writes that trailer drops the `with:`
|
|
110
|
+
block.
|
|
111
|
+
|
|
112
|
+
Its inputs, all optional:
|
|
113
|
+
|
|
114
|
+
- `checks`: names separated by white space or commas, each passed as `--check`.
|
|
115
|
+
- `except`: names, each passed as `--except`. Not allowed together with `checks`.
|
|
116
|
+
- `format`: `text` (the default) or `json`.
|
|
117
|
+
- `config`: a path passed as `--config`. It is read from the checked-out tree, which in
|
|
118
|
+
a pull request is the pull request's own copy; the default, empty, reads
|
|
119
|
+
`repo-truth.json` at the merge base instead.
|
|
120
|
+
- `base`: a revision passed as `--base`. The range is then from that revision to the
|
|
121
|
+
commit the run is for, on any event: a push, a merge queue, a scheduled or a manual
|
|
122
|
+
run. Empty keeps the pull request's range.
|
|
123
|
+
|
|
124
|
+
A tag can be moved after you adopt it, and RT-88 below reports exactly that kind of
|
|
125
|
+
reference. To hold the Action to one commit, write the full commit id the tag names
|
|
126
|
+
in place of `v0.1.0`.
|
|
127
|
+
|
|
128
|
+
## Configuration
|
|
129
|
+
|
|
130
|
+
Options for the checks that take them live in `repo-truth.json` at the top of the
|
|
131
|
+
repository. It is read from the base commit through git, not from the working tree or
|
|
132
|
+
the candidate, so a range is judged by the settings it started from; RT-66 fails a
|
|
133
|
+
range that changes the file. `--config <path>` reads another file instead.
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"schema": "repo-truth.config/v1",
|
|
138
|
+
"checks": {
|
|
139
|
+
"RT-57": { "forbidden": ["migrations/**", ".github/CODEOWNERS"] },
|
|
140
|
+
"RT-88": { "allowedOwners": ["actions"] }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
A missing or wrong `schema`, a key other than `schema` and `checks`, a check the
|
|
146
|
+
registry does not hold, an option the check does not declare or a value of the wrong
|
|
147
|
+
kind is a refusal, exit 2, naming each issue. The options:
|
|
148
|
+
|
|
149
|
+
- `RT-12` `maxBytes` (integer): the largest blob the range may bring in.
|
|
150
|
+
- `RT-18` `registries` (strings): the registries a dependency may come from.
|
|
151
|
+
- `RT-19` `timeoutMs` (integer): how long one replayed command may run.
|
|
152
|
+
- `RT-55` `allowedSuffixes` (strings): binary files whose names end so are not reported.
|
|
153
|
+
- `RT-57` `forbidden` and `allowed` (strings): path patterns. `?` is one character but
|
|
154
|
+
`/`, `*` is any run but `/`, and a segment that is exactly `**` is any number of
|
|
155
|
+
whole segments.
|
|
156
|
+
- `RT-88` `allowedOwners` (strings): action owners whose references need no pin.
|
|
157
|
+
|
|
158
|
+
Many findings are for a human to accept rather than to fix. Eighteen checks accept a
|
|
159
|
+
sanction: RT-04, RT-14, RT-16, RT-24, RT-31, RT-33, RT-34, RT-55, RT-56, RT-57, RT-66,
|
|
160
|
+
RT-87, RT-88, RT-89, RT-91, RT-92, RT-93 and RT-94. A line
|
|
161
|
+
`Sanctioned-Weakening: <id> <reason>` in a commit message of the range accepts the
|
|
162
|
+
finding with that id, and the record then shows the reason. The header comment of
|
|
163
|
+
each check's module states the form of its ids: `<category>:<file>` for some,
|
|
164
|
+
`<category>:<file>:<line>` for others such as RT-04, RT-24 and RT-31. RT-04's id opens
|
|
165
|
+
its evidence line, as in `skip:test/math.test.js:4`; other checks print the same parts
|
|
166
|
+
in another order, as `<file>: <category>`, so the id is not always the evidence line's
|
|
167
|
+
first word. The other thirteen checks have no sanction: the fix is to change the range.
|
|
168
|
+
|
|
169
|
+
## Checks
|
|
170
|
+
|
|
171
|
+
There are 31 checks in version 0.1.0: `repo-truth list | wc -l` printed 31 on
|
|
172
|
+
2026-11-02, run from the package as it was packed for this release. In the order
|
|
173
|
+
they run:
|
|
174
|
+
|
|
175
|
+
- `RT-01` commit hygiene: the message and both identities of every commit hold only
|
|
176
|
+
printable ASCII and tab.
|
|
177
|
+
- `RT-02` measurement rule: a figure with a unit, in a commit body or in a line the
|
|
178
|
+
range adds to a `.md` file, has a command (a code span or a fenced block) and an ISO
|
|
179
|
+
date in the same paragraph or commit body.
|
|
180
|
+
- `RT-03` trailer: the candidate commit names the task it closes in a `Task-Id` or a
|
|
181
|
+
`Millwright-Task-ID` trailer.
|
|
182
|
+
- `RT-04` weakened tests: no skip or focus marker added, no test file, test or
|
|
183
|
+
assertion removed.
|
|
184
|
+
- `RT-05` stray files: no added path is a log, an editor backup, a set-aside copy, a
|
|
185
|
+
scratch script, coverage or build output, OS metadata, or a path the repository's
|
|
186
|
+
own ignore rules exclude.
|
|
187
|
+
- `RT-06` lockfile drift: the dependency fields of `package.json` and the root
|
|
188
|
+
lockfile change together.
|
|
189
|
+
- `RT-09` conflict markers: no added line is a leftover merge conflict marker.
|
|
190
|
+
- `RT-10` portable paths: no new name differs from another only in letter case, is a
|
|
191
|
+
Windows device name, ends in a dot or a space, or holds a character Windows forbids.
|
|
192
|
+
- `RT-11` symlinks: no added or changed link points at an absolute path, outside the
|
|
193
|
+
repository, or into `.git`.
|
|
194
|
+
- `RT-12` large blobs: no blob the range brings into the history is larger than
|
|
195
|
+
`maxBytes`, counting blobs the candidate's tree no longer holds.
|
|
196
|
+
- `RT-14` lowered thresholds: no coverage minimum goes down or goes away.
|
|
197
|
+
- `RT-16` snapshot refreshes: every snapshot the range rewrites or deletes, and whether
|
|
198
|
+
the commit that did it also changed a file that is neither a snapshot nor a test.
|
|
199
|
+
- `RT-18` dependency sources: every added dependency comes from a registry, not a git
|
|
200
|
+
URL, a tarball URL, a path or an alias.
|
|
201
|
+
- `RT-19` replayed figures: a figure written as a git command in a code span, the word
|
|
202
|
+
`printed` and the output in a code span is run again when it is a read-only git
|
|
203
|
+
command the check knows how to replay, and a different output fails; any other
|
|
204
|
+
command is counted as not replayable.
|
|
205
|
+
- `RT-88` unpinned actions: every `uses:` the range adds to a workflow or an
|
|
206
|
+
`action.yml` names a full commit id, a digest or a local path.
|
|
207
|
+
- `RT-29` autosquash leftovers: no commit subject begins `fixup! `, `squash! ` or
|
|
208
|
+
`amend! `.
|
|
209
|
+
- `RT-93` bidirectional controls: no file the range touches gains one of the nine
|
|
210
|
+
Unicode controls that reorder how a line is shown ("Trojan Source").
|
|
211
|
+
- `RT-55` binary files: no file the range adds or changes is binary by git's own test.
|
|
212
|
+
- `RT-66` configuration changed: the range does not add, change, delete or rename the
|
|
213
|
+
file the run's settings were read from.
|
|
214
|
+
- `RT-26` credentials: no added line holds a credential in a form only a real one has;
|
|
215
|
+
the finding names the file, the line and the kind, never the text.
|
|
216
|
+
- `RT-94` install scripts: no package the range adds to an npm lockfile runs an install
|
|
217
|
+
script.
|
|
218
|
+
- `RT-92` lint rules: no rule is set to `off` or `0` in an ESLint, Biome or oxlint
|
|
219
|
+
configuration file. A rule set to `warn` or deleted, and a rule turned off through
|
|
220
|
+
`extends`, are not seen.
|
|
221
|
+
- `RT-57` path policy: no changed path is one the repository's `forbidden` patterns
|
|
222
|
+
name, or outside its `allowed` ones.
|
|
223
|
+
- `RT-24` raised timeouts: no timeout in a test file or the runner's configuration goes
|
|
224
|
+
up, and none is added to a file the base already holds.
|
|
225
|
+
- `RT-33` ladder commands: every entry of `scripts` in a `package.json` that the range
|
|
226
|
+
adds, removes or changes is reported for a human to sanction.
|
|
227
|
+
- `RT-89` secret files: no added or changed file is named like one that holds secrets
|
|
228
|
+
by what it is - `.env`, `.netrc`, a private key, a Terraform state.
|
|
229
|
+
- `RT-34` compiler strictness: no strict option of a tsconfig loses its strict value,
|
|
230
|
+
and `extends` does not change.
|
|
231
|
+
- `RT-87` submodules: no submodule is added or moved, and `.gitmodules` gains no `url`.
|
|
232
|
+
- `RT-31` suppression directives: every suppression comment the range adds without
|
|
233
|
+
removing one like it from the same file.
|
|
234
|
+
- `RT-91` workflow permissions: no workflow line gives the job's token write access or
|
|
235
|
+
runs the workflow on `pull_request_target`.
|
|
236
|
+
- `RT-56` weakened workflows: no workflow is removed, and no workflow gains a line of a
|
|
237
|
+
weakening shape.
|
|
238
|
+
|
|
239
|
+
Each check is also an async function in the package's `dist/` directory: it takes
|
|
240
|
+
`{ cwd, base, candidate }` and resolves to a record of the same shape.
|
|
241
|
+
|
|
242
|
+
## Known limitations
|
|
243
|
+
|
|
244
|
+
- RT-04 reads a test renamed in its file - `test("adds")` becoming
|
|
245
|
+
`test("adds two numbers")` - as a test removed, a `test-removed` finding to
|
|
246
|
+
sanction. A test file renamed with its content unchanged is no finding.
|
|
247
|
+
- RT-06 reads any change to the root lockfile with no change to a dependency field of
|
|
248
|
+
`package.json` as an unrequested change, even one that only renames the package or
|
|
249
|
+
changes its version at the top of the lockfile.
|
|
250
|
+
- There is no `--version`: `repo-truth --version` is an unknown command, exit 2. The
|
|
251
|
+
installed version is in the package's `package.json`.
|
|
252
|
+
- The package declares no main entry, so `import "@trofimyukdev/repo-truth"` fails; a
|
|
253
|
+
module is imported by its path, `@trofimyukdev/repo-truth/dist/<module>.js`.
|
|
254
|
+
- The workflow checks (RT-56, RT-88, RT-91) read workflow files as lines of text; the
|
|
255
|
+
YAML is not parsed.
|
|
256
|
+
|
|
257
|
+
## How it is built
|
|
258
|
+
|
|
259
|
+
repo-truth's checks are built by Millwright, an autonomous coding factory, each from
|
|
260
|
+
a task in `factory/tasks/` (https://github.com/trofimyukdev/autonomous-coding-factory).
|
|
261
|
+
|
|
262
|
+
## Development
|
|
263
|
+
|
|
264
|
+
```sh
|
|
265
|
+
npm ci
|
|
266
|
+
npm run typecheck
|
|
267
|
+
npx vitest run
|
|
268
|
+
npm run build
|
|
269
|
+
node --test test/e2e/cli.e2e.mjs
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The last line runs the end-to-end suite against the built command.
|
|
273
|
+
|
|
274
|
+
License: MIT
|
package/dist/action.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Range resolution for a pull request.
|
|
3
|
+
*
|
|
4
|
+
* A pull request's range is `merge-base(target, candidate)..candidate`, never
|
|
5
|
+
* `target..candidate`: commits that landed on the target after the branch
|
|
6
|
+
* forked are not the pull request's, and a rebased branch must not be charged
|
|
7
|
+
* for them. The merge base is also what a default shallow checkout lacks, so
|
|
8
|
+
* its absence is a stated failure, never an empty range that passes.
|
|
9
|
+
*/
|
|
10
|
+
import type { CliIo } from "./cli.js";
|
|
11
|
+
/** The range cannot be resolved; the command maps this to its bad-invocation code. */
|
|
12
|
+
export declare class RangeResolutionError extends Error {
|
|
13
|
+
}
|
|
14
|
+
/** Events whose range is a pull request's. Every other event has no `--target` rule. */
|
|
15
|
+
export declare const PULL_REQUEST_EVENTS: readonly string[];
|
|
16
|
+
/**
|
|
17
|
+
* Refuse `--target` for an event that is not a pull request. An unset event
|
|
18
|
+
* name (a local run) is allowed.
|
|
19
|
+
*/
|
|
20
|
+
export declare function assertPullRequestEvent(eventName: string | undefined): void;
|
|
21
|
+
/**
|
|
22
|
+
* The merge base of two commits, as `git merge-base <target> <candidate>`
|
|
23
|
+
* prints it. Throws `RangeResolutionError` naming the missing history and the
|
|
24
|
+
* fetch depth needed when there is none.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveMergeBase(cwd: string, target: string, candidate: string): Promise<string>;
|
|
27
|
+
/**
|
|
28
|
+
* The command's arguments for the Action: `argv` as given, then one
|
|
29
|
+
* `--check <name>` per name in `env.REPO_TRUTH_CHECKS` (split at white space and
|
|
30
|
+
* commas), then one `--except <name>` per name in `env.REPO_TRUTH_EXCEPT` (split
|
|
31
|
+
* the same way), then `--format <format>` when `env.REPO_TRUTH_FORMAT` is set, then
|
|
32
|
+
* `--config <path>` when `env.REPO_TRUTH_CONFIG` is set and not empty. The range
|
|
33
|
+
* comes right after `argv`: `--base <env.REPO_TRUTH_BASE>` and `--candidate
|
|
34
|
+
* <env.GITHUB_SHA>` (`HEAD` when unset or empty) when `env.REPO_TRUTH_BASE` is set
|
|
35
|
+
* and not empty; else `--target <env.REPO_TRUTH_TARGET>` and `--candidate
|
|
36
|
+
* <env.REPO_TRUTH_CANDIDATE>` when `env.REPO_TRUTH_TARGET` is set; else none. Nothing
|
|
37
|
+
* is run, expanded or matched: every name, and the path, is one argument, as
|
|
38
|
+
* written.
|
|
39
|
+
*/
|
|
40
|
+
export declare function actionArguments(argv: readonly string[], env: Readonly<Record<string, string | undefined>>): string[];
|
|
41
|
+
/**
|
|
42
|
+
* The job summary of a run: a heading, the outcome in one sentence, and the
|
|
43
|
+
* output whole in one fenced block whose fence is longer than any run of
|
|
44
|
+
* backticks the output holds, so no text of it closes or opens a block.
|
|
45
|
+
*/
|
|
46
|
+
export declare function jobSummary(exitCode: number, output: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* What the Action's program does: run the command with the Action's
|
|
49
|
+
* arguments, pass its output on, and append the job summary to the file
|
|
50
|
+
* `env.GITHUB_STEP_SUMMARY` names, if any. Resolves to the command's exit code.
|
|
51
|
+
*/
|
|
52
|
+
export declare function runAction(argv: readonly string[], env: Readonly<Record<string, string | undefined>>, io?: CliIo): Promise<number>;
|
package/dist/action.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Range resolution for a pull request.
|
|
3
|
+
*
|
|
4
|
+
* A pull request's range is `merge-base(target, candidate)..candidate`, never
|
|
5
|
+
* `target..candidate`: commits that landed on the target after the branch
|
|
6
|
+
* forked are not the pull request's, and a rebased branch must not be charged
|
|
7
|
+
* for them. The merge base is also what a default shallow checkout lacks, so
|
|
8
|
+
* its absence is a stated failure, never an empty range that passes.
|
|
9
|
+
*/
|
|
10
|
+
import { execFile } from "node:child_process";
|
|
11
|
+
import { appendFileSync, existsSync, readFileSync, realpathSync } from "node:fs";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
/** The range cannot be resolved; the command maps this to its bad-invocation code. */
|
|
14
|
+
export class RangeResolutionError extends Error {
|
|
15
|
+
}
|
|
16
|
+
/** Events whose range is a pull request's. Every other event has no `--target` rule. */
|
|
17
|
+
export const PULL_REQUEST_EVENTS = ["pull_request", "pull_request_target"];
|
|
18
|
+
/**
|
|
19
|
+
* Refuse `--target` for an event that is not a pull request. An unset event
|
|
20
|
+
* name (a local run) is allowed.
|
|
21
|
+
*/
|
|
22
|
+
export function assertPullRequestEvent(eventName) {
|
|
23
|
+
if (eventName === undefined || eventName === "")
|
|
24
|
+
return;
|
|
25
|
+
if (!PULL_REQUEST_EVENTS.includes(eventName)) {
|
|
26
|
+
throw new RangeResolutionError(`--target resolves a pull request's range, but this run is a ${JSON.stringify(eventName)} event; ` +
|
|
27
|
+
`name the range with --base instead`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function git(cwd, args) {
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
execFile("git", args, { cwd, maxBuffer: 64 * 1024 * 1024, encoding: "utf8" }, (error, stdout, stderr) => {
|
|
33
|
+
const code = error ? (typeof error.code === "number" ? error.code : 128) : 0;
|
|
34
|
+
resolve({ code, stdout, stderr: stderr.trim() || (error ? error.message : "") });
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The merge base of two commits, as `git merge-base <target> <candidate>`
|
|
40
|
+
* prints it. Throws `RangeResolutionError` naming the missing history and the
|
|
41
|
+
* fetch depth needed when there is none.
|
|
42
|
+
*/
|
|
43
|
+
export async function resolveMergeBase(cwd, target, candidate) {
|
|
44
|
+
const result = await git(cwd, ["merge-base", target, candidate]);
|
|
45
|
+
const base = result.stdout.trim().split("\n")[0] ?? "";
|
|
46
|
+
if (result.code === 0 && base.length > 0)
|
|
47
|
+
return base;
|
|
48
|
+
const shallow = (await git(cwd, ["rev-parse", "--is-shallow-repository"])).stdout.trim() === "true";
|
|
49
|
+
if (shallow) {
|
|
50
|
+
throw new RangeResolutionError(`the merge base of ${target} and ${candidate} is missing: this checkout is shallow and does not contain ` +
|
|
51
|
+
`the history that joins them. Check out with \`fetch-depth: 0\` (the actions/checkout input) so the full history is fetched`);
|
|
52
|
+
}
|
|
53
|
+
if (result.code === 1) {
|
|
54
|
+
throw new RangeResolutionError(`the merge base of ${target} and ${candidate} is missing: the two share no history. ` +
|
|
55
|
+
`If the checkout is partial, check out with \`fetch-depth: 0\` (the actions/checkout input)`);
|
|
56
|
+
}
|
|
57
|
+
throw new RangeResolutionError(`could not compute the merge base of ${target} and ${candidate}: ${result.stderr}`);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The command's arguments for the Action: `argv` as given, then one
|
|
61
|
+
* `--check <name>` per name in `env.REPO_TRUTH_CHECKS` (split at white space and
|
|
62
|
+
* commas), then one `--except <name>` per name in `env.REPO_TRUTH_EXCEPT` (split
|
|
63
|
+
* the same way), then `--format <format>` when `env.REPO_TRUTH_FORMAT` is set, then
|
|
64
|
+
* `--config <path>` when `env.REPO_TRUTH_CONFIG` is set and not empty. The range
|
|
65
|
+
* comes right after `argv`: `--base <env.REPO_TRUTH_BASE>` and `--candidate
|
|
66
|
+
* <env.GITHUB_SHA>` (`HEAD` when unset or empty) when `env.REPO_TRUTH_BASE` is set
|
|
67
|
+
* and not empty; else `--target <env.REPO_TRUTH_TARGET>` and `--candidate
|
|
68
|
+
* <env.REPO_TRUTH_CANDIDATE>` when `env.REPO_TRUTH_TARGET` is set; else none. Nothing
|
|
69
|
+
* is run, expanded or matched: every name, and the path, is one argument, as
|
|
70
|
+
* written.
|
|
71
|
+
*/
|
|
72
|
+
export function actionArguments(argv, env) {
|
|
73
|
+
const out = [...argv];
|
|
74
|
+
const base = env.REPO_TRUTH_BASE;
|
|
75
|
+
const sha = env.GITHUB_SHA;
|
|
76
|
+
if (base !== undefined && base !== "") {
|
|
77
|
+
out.push("--base", base, "--candidate", sha !== undefined && sha !== "" ? sha : "HEAD");
|
|
78
|
+
}
|
|
79
|
+
else if (env.REPO_TRUTH_TARGET !== undefined) {
|
|
80
|
+
out.push("--target", env.REPO_TRUTH_TARGET, "--candidate", env.REPO_TRUTH_CANDIDATE ?? "");
|
|
81
|
+
}
|
|
82
|
+
for (const name of (env.REPO_TRUTH_CHECKS ?? "").split(/[\s,]+/)) {
|
|
83
|
+
if (name !== "")
|
|
84
|
+
out.push("--check", name);
|
|
85
|
+
}
|
|
86
|
+
for (const name of (env.REPO_TRUTH_EXCEPT ?? "").split(/[\s,]+/)) {
|
|
87
|
+
if (name !== "")
|
|
88
|
+
out.push("--except", name);
|
|
89
|
+
}
|
|
90
|
+
const format = env.REPO_TRUTH_FORMAT;
|
|
91
|
+
if (format !== undefined && format !== "")
|
|
92
|
+
out.push("--format", format);
|
|
93
|
+
const config = env.REPO_TRUTH_CONFIG;
|
|
94
|
+
if (config !== undefined && config !== "")
|
|
95
|
+
out.push("--config", config);
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function isActionEntryPoint() {
|
|
99
|
+
const script = process.argv[1];
|
|
100
|
+
if (!script)
|
|
101
|
+
return false;
|
|
102
|
+
try {
|
|
103
|
+
return realpathSync(script) === realpathSync(fileURLToPath(import.meta.url));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const OUTCOMES = {
|
|
110
|
+
0: "All checks passed (exit code 0).",
|
|
111
|
+
1: "At least one check failed (exit code 1).",
|
|
112
|
+
2: "The checks did not run (exit code 2).",
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* The job summary of a run: a heading, the outcome in one sentence, and the
|
|
116
|
+
* output whole in one fenced block whose fence is longer than any run of
|
|
117
|
+
* backticks the output holds, so no text of it closes or opens a block.
|
|
118
|
+
*/
|
|
119
|
+
export function jobSummary(exitCode, output) {
|
|
120
|
+
const sentence = OUTCOMES[exitCode] ?? `The run ended with exit code ${exitCode}.`;
|
|
121
|
+
const body = output.endsWith("\n") ? output.slice(0, -1) : output;
|
|
122
|
+
const longest = Math.max(0, ...(output.match(/`+/g) ?? []).map((run) => run.length));
|
|
123
|
+
const fence = "`".repeat(Math.max(3, longest + 1));
|
|
124
|
+
return `## repo-truth\n\n${sentence}\n\n${fence}\n${body === "" ? "" : body + "\n"}${fence}\n`;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* What the Action's program does: run the command with the Action's
|
|
128
|
+
* arguments, pass its output on, and append the job summary to the file
|
|
129
|
+
* `env.GITHUB_STEP_SUMMARY` names, if any. Resolves to the command's exit code.
|
|
130
|
+
*/
|
|
131
|
+
export async function runAction(argv, env, io = {}) {
|
|
132
|
+
const { main } = await import("./cli.js");
|
|
133
|
+
let stdout = "";
|
|
134
|
+
let stderr = "";
|
|
135
|
+
const toStderr = (text) => {
|
|
136
|
+
if (io.stderr)
|
|
137
|
+
io.stderr(text);
|
|
138
|
+
else
|
|
139
|
+
process.stderr.write(text);
|
|
140
|
+
};
|
|
141
|
+
const code = await main(actionArguments(argv, env), {
|
|
142
|
+
...io,
|
|
143
|
+
stdout: (text) => {
|
|
144
|
+
stdout += text;
|
|
145
|
+
if (io.stdout)
|
|
146
|
+
io.stdout(text);
|
|
147
|
+
else
|
|
148
|
+
process.stdout.write(text);
|
|
149
|
+
},
|
|
150
|
+
stderr: (text) => {
|
|
151
|
+
stderr += text;
|
|
152
|
+
toStderr(text);
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
const file = env.GITHUB_STEP_SUMMARY;
|
|
156
|
+
if (file !== undefined && file !== "") {
|
|
157
|
+
try {
|
|
158
|
+
const held = existsSync(file) ? readFileSync(file, "utf8") : "";
|
|
159
|
+
const lead = held !== "" && !held.endsWith("\n") ? "\n" : "";
|
|
160
|
+
appendFileSync(file, lead + jobSummary(code, code === 2 ? stderr : stdout));
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
const reason = (error instanceof Error ? error.message : String(error)).replace(/\s+/g, " ");
|
|
164
|
+
toStderr(`repo-truth: could not write the job summary: ${reason}\n`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return code;
|
|
168
|
+
}
|
|
169
|
+
// cli.ts imports this module, so `main` is reached only by a dynamic import
|
|
170
|
+
// made after this module has finished loading.
|
|
171
|
+
if (isActionEntryPoint()) {
|
|
172
|
+
void (async () => {
|
|
173
|
+
try {
|
|
174
|
+
process.exitCode = await runAction(process.argv.slice(2), process.env);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
process.stderr.write(`repo-truth: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
178
|
+
process.exitCode = 2;
|
|
179
|
+
}
|
|
180
|
+
})();
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=action.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.js","sourceRoot":"","sources":["../src/action.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unpinned actions.
|
|
3
|
+
*
|
|
4
|
+
* A `uses: owner/repo@v4` runs whatever commit the tag names on the day the job
|
|
5
|
+
* runs, with the job's token and secrets. This check reads, whole and through
|
|
6
|
+
* git at the merge base of `base` and `candidate` and at `candidate` (never the
|
|
7
|
+
* working tree), every workflow file the range adds, changes or renames onto -
|
|
8
|
+
* `.github/workflows/<name>.yml` or `.yaml` directly in that directory - and
|
|
9
|
+
* every file named `action.yml` or `action.yaml` at any depth.
|
|
10
|
+
*
|
|
11
|
+
* The YAML is not parsed. A `uses:` line is a line whose text, white space at
|
|
12
|
+
* both ends removed, opens `uses:` or `- uses:`; its value is the rest of the
|
|
13
|
+
* line with a trailing comment (a `#` after white space) removed, then white
|
|
14
|
+
* space at both ends, then one pair of matching surrounding quotes. A line is
|
|
15
|
+
* added when the candidate's file holds its text more often than its source
|
|
16
|
+
* file (the file at the merge base, the one a rename came from, or none) did;
|
|
17
|
+
* a line moved or re-indented is not, and a text added more than once is one
|
|
18
|
+
* finding.
|
|
19
|
+
*
|
|
20
|
+
* Pinned: a local reference (opening `./`), a `docker://` image by digest
|
|
21
|
+
* (`@sha256:` and sixty-four hexadecimal digits), and a repository reference
|
|
22
|
+
* `<owner>/<repo>[/<path>]@<commit>` whose commit is exactly forty hexadecimal
|
|
23
|
+
* digits. Every other value - a tag, a branch, no `@`, a short commit id, a
|
|
24
|
+
* `docker://` image by tag - is unpinned and is a finding `unpinned-action`.
|
|
25
|
+
*
|
|
26
|
+
* The option `allowedOwners` (strings): a repository reference whose owner, the
|
|
27
|
+
* text before its first `/`, is one of them, compared exactly, yields no
|
|
28
|
+
* finding. `docker://` and local references are judged as above regardless.
|
|
29
|
+
*
|
|
30
|
+
* A finding's id is `unpinned-action:<file>`; it is sanctioned by a
|
|
31
|
+
* `Sanctioned-Weakening: <id> <reason>` line in a commit message of the range
|
|
32
|
+
* or by the caller's `sanctions`.
|
|
33
|
+
*/
|
|
34
|
+
import type { CheckRecord } from "../index.js";
|
|
35
|
+
export declare const name = "RT-88";
|
|
36
|
+
export declare const optionKinds: {
|
|
37
|
+
allowedOwners: string;
|
|
38
|
+
};
|
|
39
|
+
export interface ActionPinsOptions {
|
|
40
|
+
readonly cwd: string;
|
|
41
|
+
readonly base: string;
|
|
42
|
+
readonly candidate: string;
|
|
43
|
+
readonly allowedOwners?: readonly string[] | undefined;
|
|
44
|
+
/** Ids (`unpinned-action:<file>`) mapped to reasons. */
|
|
45
|
+
readonly sanctions?: Readonly<Record<string, string>> | undefined;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Report each unpinned `uses:` reference `base..candidate` adds. Fails while any
|
|
49
|
+
* finding is unsanctioned, and fails closed: a revision that names no commit,
|
|
50
|
+
* or any git invocation that fails, is a `fail` record naming the cause.
|
|
51
|
+
*/
|
|
52
|
+
export declare function check(options: ActionPinsOptions): Promise<CheckRecord>;
|