@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
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
- # Temporary Holding Version
1
+ # repo-truth
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
@@ -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":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAIzC,sFAAsF;AACtF,MAAM,OAAO,oBAAqB,SAAQ,KAAK;CAAG;AAElD,wFAAwF;AACxF,MAAM,CAAC,MAAM,mBAAmB,GAAsB,CAAC,cAAc,EAAE,qBAAqB,CAAC,CAAC;AAE9F;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,SAA6B;IAClE,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,EAAE;QAAE,OAAO;IACxD,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,oBAAoB,CAC5B,+DAA+D,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,UAAU;YAChG,oCAAoC,CACvC,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,GAAG,CAAC,GAAW,EAAE,IAAuB;IAC/C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,QAAQ,CACN,KAAK,EACL,IAAgB,EAChB,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EACtD,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACxB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7E,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACnF,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAW,EAAE,MAAc,EAAE,SAAiB;IACnF,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACvD,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAEtD,MAAM,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,MAAM,CAAC;IACpG,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,IAAI,oBAAoB,CAC5B,qBAAqB,MAAM,QAAQ,SAAS,6DAA6D;YACvG,4HAA4H,CAC/H,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,oBAAoB,CAC5B,qBAAqB,MAAM,QAAQ,SAAS,yCAAyC;YACnF,4FAA4F,CAC/F,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,oBAAoB,CAAC,uCAAuC,MAAM,QAAQ,SAAS,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AACrH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,eAAe,CAAC,IAAuB,EAAE,GAAiD;IACxG,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IACtB,MAAM,IAAI,GAAG,GAAG,CAAC,eAAe,CAAC;IACjC,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC;IAC3B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;QACtC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC1F,CAAC;SAAM,IAAI,GAAG,CAAC,iBAAiB,KAAK,SAAS,EAAE,CAAC;QAC/C,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,CAAC,iBAAiB,EAAE,aAAa,EAAE,GAAG,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;IAC7F,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjE,IAAI,IAAI,KAAK,EAAE;YAAE,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjE,IAAI,IAAI,KAAK,EAAE;YAAE,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAC9C,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,iBAAiB,CAAC;IACrC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE;QAAE,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,GAAG,CAAC,iBAAiB,CAAC;IACrC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE;QAAE,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACxE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,kBAAkB;IACzB,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC/B,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,MAAM,CAAC,KAAK,YAAY,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,QAAQ,GAAqC;IACjD,CAAC,EAAE,kCAAkC;IACrC,CAAC,EAAE,0CAA0C;IAC7C,CAAC,EAAE,uCAAuC;CAC3C,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,QAAgB,EAAE,MAAc;IACzD,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,IAAI,gCAAgC,QAAQ,GAAG,CAAC;IACnF,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAClE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IACrF,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;IACnD,OAAO,oBAAoB,QAAQ,OAAO,KAAK,KAAK,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,GAAG,KAAK,IAAI,CAAC;AACjG,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,IAAuB,EACvB,GAAiD,EACjD,KAAY,EAAE;IAEd,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;IAC1C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAQ,EAAE;QACtC,IAAI,EAAE,CAAC,MAAM;YAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;;YAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC,CAAC;IACF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE;QAClD,GAAG,EAAE;QACL,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACf,MAAM,IAAI,IAAI,CAAC;YACf,IAAI,EAAE,CAAC,MAAM;gBAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;;gBAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QACD,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACf,MAAM,IAAI,IAAI,CAAC;YACf,QAAQ,CAAC,IAAI,CAAC,CAAC;QACjB,CAAC;KACF,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,GAAG,CAAC,mBAAmB,CAAC;IACrC,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;QACtC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAChE,MAAM,IAAI,GAAG,IAAI,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAC9E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC7F,QAAQ,CAAC,gDAAgD,MAAM,IAAI,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,4EAA4E;AAC5E,+CAA+C;AAC/C,IAAI,kBAAkB,EAAE,EAAE,CAAC;IACzB,KAAK,CAAC,KAAK,IAAI,EAAE;QACf,IAAI,CAAC;YACH,OAAO,CAAC,QAAQ,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QACzE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAChG,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;AACP,CAAC"}
@@ -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>;