pr-merge-queue 0.0.0-stage → 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +125 -2
- package/dist/claim-pr/claim-pr.d.mts +45 -0
- package/dist/claim-pr/claim-pr.d.mts.map +1 -0
- package/dist/claim-pr/claim-pr.mjs +217 -0
- package/dist/claim-pr/claim-pr.mjs.map +1 -0
- package/dist/claim-pr/index.d.mts +2 -0
- package/dist/claim-pr/index.d.mts.map +1 -0
- package/dist/claim-pr/index.mjs +2 -0
- package/dist/claim-pr/index.mjs.map +1 -0
- package/dist/cmd/claim-pr.d.mts +3 -0
- package/dist/cmd/claim-pr.d.mts.map +1 -0
- package/dist/cmd/claim-pr.mjs +20 -0
- package/dist/cmd/claim-pr.mjs.map +1 -0
- package/dist/cmd/open-pr.d.mts +3 -0
- package/dist/cmd/open-pr.d.mts.map +1 -0
- package/dist/cmd/open-pr.mjs +20 -0
- package/dist/cmd/open-pr.mjs.map +1 -0
- package/dist/cmd/pr-report.d.mts +3 -0
- package/dist/cmd/pr-report.d.mts.map +1 -0
- package/dist/cmd/pr-report.mjs +20 -0
- package/dist/cmd/pr-report.mjs.map +1 -0
- package/dist/cmd/push-fixed-pr.d.mts +3 -0
- package/dist/cmd/push-fixed-pr.d.mts.map +1 -0
- package/dist/cmd/push-fixed-pr.mjs +20 -0
- package/dist/cmd/push-fixed-pr.mjs.map +1 -0
- package/dist/cmd/report-unowned-prs.d.mts +3 -0
- package/dist/cmd/report-unowned-prs.d.mts.map +1 -0
- package/dist/cmd/report-unowned-prs.mjs +20 -0
- package/dist/cmd/report-unowned-prs.mjs.map +1 -0
- package/dist/cmd/unblock-prs.d.mts +3 -0
- package/dist/cmd/unblock-prs.d.mts.map +1 -0
- package/dist/cmd/unblock-prs.mjs +19 -0
- package/dist/cmd/unblock-prs.mjs.map +1 -0
- package/dist/entry-point.d.mts +3 -0
- package/dist/entry-point.d.mts.map +1 -0
- package/dist/entry-point.mjs +3 -0
- package/dist/entry-point.mjs.map +1 -0
- package/dist/lib/cli-args.d.mts +27 -0
- package/dist/lib/cli-args.d.mts.map +1 -0
- package/dist/lib/cli-args.mjs +55 -0
- package/dist/lib/cli-args.mjs.map +1 -0
- package/dist/lib/github-api.d.mts +153 -0
- package/dist/lib/github-api.d.mts.map +1 -0
- package/dist/lib/github-api.mjs +291 -0
- package/dist/lib/github-api.mjs.map +1 -0
- package/dist/lib/index.d.mts +7 -0
- package/dist/lib/index.d.mts.map +1 -0
- package/dist/lib/index.mjs +7 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/json.d.mts +12 -0
- package/dist/lib/json.d.mts.map +1 -0
- package/dist/lib/json.mjs +28 -0
- package/dist/lib/json.mjs.map +1 -0
- package/dist/lib/log.d.mts +10 -0
- package/dist/lib/log.d.mts.map +1 -0
- package/dist/lib/log.mjs +13 -0
- package/dist/lib/log.mjs.map +1 -0
- package/dist/lib/shell.d.mts +18 -0
- package/dist/lib/shell.d.mts.map +1 -0
- package/dist/lib/shell.mjs +42 -0
- package/dist/lib/shell.mjs.map +1 -0
- package/dist/lib/time.d.mts +17 -0
- package/dist/lib/time.d.mts.map +1 -0
- package/dist/lib/time.mjs +35 -0
- package/dist/lib/time.mjs.map +1 -0
- package/dist/open-pr/github.d.mts +72 -0
- package/dist/open-pr/github.d.mts.map +1 -0
- package/dist/open-pr/github.mjs +249 -0
- package/dist/open-pr/github.mjs.map +1 -0
- package/dist/open-pr/index.d.mts +6 -0
- package/dist/open-pr/index.d.mts.map +1 -0
- package/dist/open-pr/index.mjs +6 -0
- package/dist/open-pr/index.mjs.map +1 -0
- package/dist/open-pr/main.d.mts +36 -0
- package/dist/open-pr/main.d.mts.map +1 -0
- package/dist/open-pr/main.mjs +204 -0
- package/dist/open-pr/main.mjs.map +1 -0
- package/dist/open-pr/options.d.mts +20 -0
- package/dist/open-pr/options.d.mts.map +1 -0
- package/dist/open-pr/options.mjs +66 -0
- package/dist/open-pr/options.mjs.map +1 -0
- package/dist/open-pr/steps.d.mts +23 -0
- package/dist/open-pr/steps.d.mts.map +1 -0
- package/dist/open-pr/steps.mjs +38 -0
- package/dist/open-pr/steps.mjs.map +1 -0
- package/dist/open-pr/types.d.mts +179 -0
- package/dist/open-pr/types.d.mts.map +1 -0
- package/dist/open-pr/types.mjs +20 -0
- package/dist/open-pr/types.mjs.map +1 -0
- package/dist/pr-report/github.d.mts +59 -0
- package/dist/pr-report/github.d.mts.map +1 -0
- package/dist/pr-report/github.mjs +561 -0
- package/dist/pr-report/github.mjs.map +1 -0
- package/dist/pr-report/index.d.mts +8 -0
- package/dist/pr-report/index.d.mts.map +1 -0
- package/dist/pr-report/index.mjs +8 -0
- package/dist/pr-report/index.mjs.map +1 -0
- package/dist/pr-report/linked-issues.d.mts +15 -0
- package/dist/pr-report/linked-issues.d.mts.map +1 -0
- package/dist/pr-report/linked-issues.mjs +39 -0
- package/dist/pr-report/linked-issues.mjs.map +1 -0
- package/dist/pr-report/main.d.mts +42 -0
- package/dist/pr-report/main.d.mts.map +1 -0
- package/dist/pr-report/main.mjs +163 -0
- package/dist/pr-report/main.mjs.map +1 -0
- package/dist/pr-report/options.d.mts +36 -0
- package/dist/pr-report/options.d.mts.map +1 -0
- package/dist/pr-report/options.mjs +90 -0
- package/dist/pr-report/options.mjs.map +1 -0
- package/dist/pr-report/queue.d.mts +28 -0
- package/dist/pr-report/queue.d.mts.map +1 -0
- package/dist/pr-report/queue.mjs +74 -0
- package/dist/pr-report/queue.mjs.map +1 -0
- package/dist/pr-report/render.d.mts +14 -0
- package/dist/pr-report/render.d.mts.map +1 -0
- package/dist/pr-report/render.mjs +318 -0
- package/dist/pr-report/render.mjs.map +1 -0
- package/dist/pr-report/repo-settings.d.mts +12 -0
- package/dist/pr-report/repo-settings.d.mts.map +1 -0
- package/dist/pr-report/repo-settings.mjs +22 -0
- package/dist/pr-report/repo-settings.mjs.map +1 -0
- package/dist/push-fixed-pr/index.d.mts +2 -0
- package/dist/push-fixed-pr/index.d.mts.map +1 -0
- package/dist/push-fixed-pr/index.mjs +2 -0
- package/dist/push-fixed-pr/index.mjs.map +1 -0
- package/dist/push-fixed-pr/push-fixed-pr.d.mts +54 -0
- package/dist/push-fixed-pr/push-fixed-pr.d.mts.map +1 -0
- package/dist/push-fixed-pr/push-fixed-pr.mjs +172 -0
- package/dist/push-fixed-pr/push-fixed-pr.mjs.map +1 -0
- package/dist/report-unowned-prs/index.d.mts +2 -0
- package/dist/report-unowned-prs/index.d.mts.map +1 -0
- package/dist/report-unowned-prs/index.mjs +2 -0
- package/dist/report-unowned-prs/index.mjs.map +1 -0
- package/dist/report-unowned-prs/report-unowned-prs.d.mts +49 -0
- package/dist/report-unowned-prs/report-unowned-prs.d.mts.map +1 -0
- package/dist/report-unowned-prs/report-unowned-prs.mjs +127 -0
- package/dist/report-unowned-prs/report-unowned-prs.mjs.map +1 -0
- package/dist/tsconfig.json +1 -0
- package/dist/unblock-prs/auto-fix.d.mts +45 -0
- package/dist/unblock-prs/auto-fix.d.mts.map +1 -0
- package/dist/unblock-prs/auto-fix.mjs +207 -0
- package/dist/unblock-prs/auto-fix.mjs.map +1 -0
- package/dist/unblock-prs/checks.d.mts +307 -0
- package/dist/unblock-prs/checks.d.mts.map +1 -0
- package/dist/unblock-prs/checks.mjs +372 -0
- package/dist/unblock-prs/checks.mjs.map +1 -0
- package/dist/unblock-prs/claims.d.mts +21 -0
- package/dist/unblock-prs/claims.d.mts.map +1 -0
- package/dist/unblock-prs/claims.mjs +23 -0
- package/dist/unblock-prs/claims.mjs.map +1 -0
- package/dist/unblock-prs/config.d.mts +487 -0
- package/dist/unblock-prs/config.d.mts.map +1 -0
- package/dist/unblock-prs/config.mjs +119 -0
- package/dist/unblock-prs/config.mjs.map +1 -0
- package/dist/unblock-prs/constants.d.mts +28 -0
- package/dist/unblock-prs/constants.d.mts.map +1 -0
- package/dist/unblock-prs/constants.mjs +28 -0
- package/dist/unblock-prs/constants.mjs.map +1 -0
- package/dist/unblock-prs/demotions.d.mts +42 -0
- package/dist/unblock-prs/demotions.d.mts.map +1 -0
- package/dist/unblock-prs/demotions.mjs +50 -0
- package/dist/unblock-prs/demotions.mjs.map +1 -0
- package/dist/unblock-prs/github.d.mts +739 -0
- package/dist/unblock-prs/github.d.mts.map +1 -0
- package/dist/unblock-prs/github.mjs +626 -0
- package/dist/unblock-prs/github.mjs.map +1 -0
- package/dist/unblock-prs/index.d.mts +25 -0
- package/dist/unblock-prs/index.d.mts.map +1 -0
- package/dist/unblock-prs/index.mjs +25 -0
- package/dist/unblock-prs/index.mjs.map +1 -0
- package/dist/unblock-prs/labels.d.mts +49 -0
- package/dist/unblock-prs/labels.d.mts.map +1 -0
- package/dist/unblock-prs/labels.mjs +49 -0
- package/dist/unblock-prs/labels.mjs.map +1 -0
- package/dist/unblock-prs/main.d.mts +179 -0
- package/dist/unblock-prs/main.d.mts.map +1 -0
- package/dist/unblock-prs/main.mjs +640 -0
- package/dist/unblock-prs/main.mjs.map +1 -0
- package/dist/unblock-prs/merge-after.d.mts +46 -0
- package/dist/unblock-prs/merge-after.d.mts.map +1 -0
- package/dist/unblock-prs/merge-after.mjs +65 -0
- package/dist/unblock-prs/merge-after.mjs.map +1 -0
- package/dist/unblock-prs/options.d.mts +35 -0
- package/dist/unblock-prs/options.d.mts.map +1 -0
- package/dist/unblock-prs/options.mjs +88 -0
- package/dist/unblock-prs/options.mjs.map +1 -0
- package/dist/unblock-prs/owner.d.mts +32 -0
- package/dist/unblock-prs/owner.d.mts.map +1 -0
- package/dist/unblock-prs/owner.mjs +63 -0
- package/dist/unblock-prs/owner.mjs.map +1 -0
- package/dist/unblock-prs/quiet.d.mts +21 -0
- package/dist/unblock-prs/quiet.d.mts.map +1 -0
- package/dist/unblock-prs/quiet.mjs +42 -0
- package/dist/unblock-prs/quiet.mjs.map +1 -0
- package/dist/unblock-prs/rebase.d.mts +120 -0
- package/dist/unblock-prs/rebase.d.mts.map +1 -0
- package/dist/unblock-prs/rebase.mjs +576 -0
- package/dist/unblock-prs/rebase.mjs.map +1 -0
- package/dist/unblock-prs/release.d.mts +62 -0
- package/dist/unblock-prs/release.d.mts.map +1 -0
- package/dist/unblock-prs/release.mjs +94 -0
- package/dist/unblock-prs/release.mjs.map +1 -0
- package/dist/unblock-prs/set-aside-comment.d.mts +20 -0
- package/dist/unblock-prs/set-aside-comment.d.mts.map +1 -0
- package/dist/unblock-prs/set-aside-comment.mjs +93 -0
- package/dist/unblock-prs/set-aside-comment.mjs.map +1 -0
- package/dist/unblock-prs/set-aside-detail.d.mts +41 -0
- package/dist/unblock-prs/set-aside-detail.d.mts.map +1 -0
- package/dist/unblock-prs/set-aside-detail.mjs +56 -0
- package/dist/unblock-prs/set-aside-detail.mjs.map +1 -0
- package/dist/unblock-prs/skips.d.mts +85 -0
- package/dist/unblock-prs/skips.d.mts.map +1 -0
- package/dist/unblock-prs/skips.mjs +141 -0
- package/dist/unblock-prs/skips.mjs.map +1 -0
- package/dist/unblock-prs/stack.d.mts +98 -0
- package/dist/unblock-prs/stack.d.mts.map +1 -0
- package/dist/unblock-prs/stack.mjs +141 -0
- package/dist/unblock-prs/stack.mjs.map +1 -0
- package/dist/unblock-prs/triage.d.mts +28 -0
- package/dist/unblock-prs/triage.d.mts.map +1 -0
- package/dist/unblock-prs/triage.mjs +396 -0
- package/dist/unblock-prs/triage.mjs.map +1 -0
- package/dist/unblock-prs/types.d.mts +793 -0
- package/dist/unblock-prs/types.d.mts.map +1 -0
- package/dist/unblock-prs/types.mjs +26 -0
- package/dist/unblock-prs/types.mjs.map +1 -0
- package/dist/unblock-prs/util.d.mts +23 -0
- package/dist/unblock-prs/util.d.mts.map +1 -0
- package/dist/unblock-prs/util.mjs +60 -0
- package/dist/unblock-prs/util.mjs.map +1 -0
- package/dist/unblock-prs/version-pr.d.mts +57 -0
- package/dist/unblock-prs/version-pr.d.mts.map +1 -0
- package/dist/unblock-prs/version-pr.mjs +79 -0
- package/dist/unblock-prs/version-pr.mjs.map +1 -0
- package/dist/unblock-prs/watch.d.mts +17 -0
- package/dist/unblock-prs/watch.d.mts.map +1 -0
- package/dist/unblock-prs/watch.mjs +128 -0
- package/dist/unblock-prs/watch.mjs.map +1 -0
- package/package.json +118 -3
- package/src/claim-pr/claim-pr.mts +380 -0
- package/src/claim-pr/claim-pr.test.mts +176 -0
- package/src/claim-pr/index.mts +1 -0
- package/src/cmd/claim-pr.mts +32 -0
- package/src/cmd/open-pr.mts +28 -0
- package/src/cmd/pr-report.mts +28 -0
- package/src/cmd/push-fixed-pr.mts +36 -0
- package/src/cmd/report-unowned-prs.mts +37 -0
- package/src/cmd/unblock-prs.mts +24 -0
- package/src/entry-point.mts +3 -0
- package/src/lib/cli-args.mts +86 -0
- package/src/lib/cli-args.test.mts +92 -0
- package/src/lib/github-api.mts +463 -0
- package/src/lib/github-api.test.mts +40 -0
- package/src/lib/index.mts +6 -0
- package/src/lib/json.mts +43 -0
- package/src/lib/json.test.mts +34 -0
- package/src/lib/log.mts +19 -0
- package/src/lib/log.test.mts +45 -0
- package/src/lib/shell.mts +52 -0
- package/src/lib/time.mts +49 -0
- package/src/open-pr/README.md +237 -0
- package/src/open-pr/github.mts +444 -0
- package/src/open-pr/index.mts +5 -0
- package/src/open-pr/main.mts +327 -0
- package/src/open-pr/options.mts +101 -0
- package/src/open-pr/options.test.mts +67 -0
- package/src/open-pr/steps.mts +57 -0
- package/src/open-pr/steps.test.mts +59 -0
- package/src/open-pr/types.mts +42 -0
- package/src/pr-report/README.md +244 -0
- package/src/pr-report/github.mts +911 -0
- package/src/pr-report/index.mts +7 -0
- package/src/pr-report/linked-issues.mts +51 -0
- package/src/pr-report/linked-issues.test.mts +65 -0
- package/src/pr-report/main.mts +257 -0
- package/src/pr-report/options.mts +140 -0
- package/src/pr-report/queue.mts +125 -0
- package/src/pr-report/render.mts +428 -0
- package/src/pr-report/render.test.mts +379 -0
- package/src/pr-report/repo-settings.mts +34 -0
- package/src/push-fixed-pr/index.mts +1 -0
- package/src/push-fixed-pr/push-fixed-pr.mts +276 -0
- package/src/push-fixed-pr/push-fixed-pr.test.mts +228 -0
- package/src/report-unowned-prs/index.mts +1 -0
- package/src/report-unowned-prs/report-unowned-prs.mts +190 -0
- package/src/report-unowned-prs/report-unowned-prs.test.mts +87 -0
- package/src/unblock-prs/README.md +1616 -0
- package/src/unblock-prs/auto-fix.mts +334 -0
- package/src/unblock-prs/auto-fix.test.mts +159 -0
- package/src/unblock-prs/checks.mts +538 -0
- package/src/unblock-prs/checks.test.mts +507 -0
- package/src/unblock-prs/claims.mts +33 -0
- package/src/unblock-prs/claims.test.mts +64 -0
- package/src/unblock-prs/config.mts +187 -0
- package/src/unblock-prs/config.schema.json +71 -0
- package/src/unblock-prs/config.test.mts +139 -0
- package/src/unblock-prs/constants.mts +33 -0
- package/src/unblock-prs/demotions.mts +73 -0
- package/src/unblock-prs/demotions.test.mts +100 -0
- package/src/unblock-prs/github.mts +1042 -0
- package/src/unblock-prs/github.test.mts +84 -0
- package/src/unblock-prs/index.mts +24 -0
- package/src/unblock-prs/labels.mts +70 -0
- package/src/unblock-prs/main.mts +1051 -0
- package/src/unblock-prs/merge-after.mts +117 -0
- package/src/unblock-prs/merge-after.test.mts +120 -0
- package/src/unblock-prs/options.mts +157 -0
- package/src/unblock-prs/owner.mts +91 -0
- package/src/unblock-prs/owner.test.mts +114 -0
- package/src/unblock-prs/quiet.mts +62 -0
- package/src/unblock-prs/quiet.test.mts +175 -0
- package/src/unblock-prs/rebase.mts +962 -0
- package/src/unblock-prs/release.mts +149 -0
- package/src/unblock-prs/release.test.mts +209 -0
- package/src/unblock-prs/set-aside-comment.mts +143 -0
- package/src/unblock-prs/set-aside-comment.test.mts +148 -0
- package/src/unblock-prs/set-aside-detail.mts +82 -0
- package/src/unblock-prs/set-aside-detail.test.mts +131 -0
- package/src/unblock-prs/skips.mts +215 -0
- package/src/unblock-prs/skips.test.mts +280 -0
- package/src/unblock-prs/stack.mts +220 -0
- package/src/unblock-prs/stack.test.mts +449 -0
- package/src/unblock-prs/triage.mts +669 -0
- package/src/unblock-prs/triage.test.mts +100 -0
- package/src/unblock-prs/types.mts +358 -0
- package/src/unblock-prs/util.mts +78 -0
- package/src/unblock-prs/util.test.mts +26 -0
- package/src/unblock-prs/version-pr.mts +112 -0
- package/src/unblock-prs/version-pr.test.mts +78 -0
- package/src/unblock-prs/watch.mts +219 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** The issues a pull request body says it closes. */
|
|
2
|
+
|
|
3
|
+
import { outsideCodeFences, type RepoRef } from 'pr-report-core';
|
|
4
|
+
import { Arr, Num, Result } from 'ts-data-forge';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The keywords GitHub acts on, followed by `#12` or by the issue's URL.
|
|
8
|
+
*
|
|
9
|
+
* Only a closing keyword is read. A body mentioning `#12` in a sentence is a
|
|
10
|
+
* cross-reference — GitHub closes nothing for it, and reporting it as "the
|
|
11
|
+
* issue this pull request is for" would make every mention of another pull
|
|
12
|
+
* request look like a link.
|
|
13
|
+
*/
|
|
14
|
+
const CLOSING_REFERENCE =
|
|
15
|
+
/\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\b[^\S\n]*:?[^\S\n]*(?:#(?<number>\d+)|https:\/\/github\.com\/(?<owner>[\w.-]+)\/(?<repo>[\w.-]+)\/issues\/(?<urlNumber>\d+))\b/giu;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The issue numbers a body declares this pull request closes, in the order
|
|
19
|
+
* they first appear.
|
|
20
|
+
*
|
|
21
|
+
* Read from the body rather than from GitHub's own list because that list is
|
|
22
|
+
* only in the GraphQL API, which needs a token. What this misses is a link
|
|
23
|
+
* made by hand in the sidebar, and a reference to another repository's issue
|
|
24
|
+
* — deliberately, since neither can be rendered as a local `#N`.
|
|
25
|
+
*
|
|
26
|
+
* Fenced code is skipped for the same reason `Merge-After:` skips it.
|
|
27
|
+
*/
|
|
28
|
+
export const parseClosingIssueRefs = (
|
|
29
|
+
body: string,
|
|
30
|
+
repo: RepoRef,
|
|
31
|
+
): readonly number[] =>
|
|
32
|
+
Arr.uniq(
|
|
33
|
+
outsideCodeFences(body)
|
|
34
|
+
.matchAll(CLOSING_REFERENCE)
|
|
35
|
+
.flatMap((match) => {
|
|
36
|
+
const { number, owner, repo: name, urlNumber } = match.groups ?? {};
|
|
37
|
+
|
|
38
|
+
return number !== undefined
|
|
39
|
+
? readNumber(number)
|
|
40
|
+
: owner === repo.owner && name === repo.name
|
|
41
|
+
? readNumber(urlNumber ?? '')
|
|
42
|
+
: [];
|
|
43
|
+
})
|
|
44
|
+
.toArray(),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const readNumber = (raw: string): readonly number[] => {
|
|
48
|
+
const parsed = Num.safeParseInt(raw);
|
|
49
|
+
|
|
50
|
+
return Result.isOk(parsed) ? [parsed.value] : [];
|
|
51
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { parseClosingIssueRefs } from './linked-issues.mjs';
|
|
2
|
+
|
|
3
|
+
const repo = { owner: 'noshiro-pf', name: 'mono' } as const;
|
|
4
|
+
|
|
5
|
+
describe(parseClosingIssueRefs, () => {
|
|
6
|
+
test('reads every keyword GitHub reads', () => {
|
|
7
|
+
for (const keyword of [
|
|
8
|
+
'close',
|
|
9
|
+
'closes',
|
|
10
|
+
'closed',
|
|
11
|
+
'fix',
|
|
12
|
+
'fixes',
|
|
13
|
+
'fixed',
|
|
14
|
+
'resolve',
|
|
15
|
+
'resolves',
|
|
16
|
+
'resolved',
|
|
17
|
+
]) {
|
|
18
|
+
assert.deepStrictEqual(
|
|
19
|
+
parseClosingIssueRefs(`${keyword} #12`, repo),
|
|
20
|
+
[12],
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('is case-insensitive and accepts the colon form', () => {
|
|
26
|
+
assert.deepStrictEqual(parseClosingIssueRefs('Closes: #12', repo), [12]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('reads a full URL, but only for this repository', () => {
|
|
30
|
+
assert.deepStrictEqual(
|
|
31
|
+
parseClosingIssueRefs(
|
|
32
|
+
'Fixes https://github.com/noshiro-pf/mono/issues/34',
|
|
33
|
+
repo,
|
|
34
|
+
),
|
|
35
|
+
[34],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
assert.deepStrictEqual(
|
|
39
|
+
parseClosingIssueRefs(
|
|
40
|
+
'Fixes https://github.com/someone/else/issues/34',
|
|
41
|
+
repo,
|
|
42
|
+
),
|
|
43
|
+
[],
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('a bare reference is not a link', () => {
|
|
48
|
+
// `#12` on its own is a cross-reference; GitHub closes nothing for it,
|
|
49
|
+
// and neither does this.
|
|
50
|
+
assert.deepStrictEqual(parseClosingIssueRefs('see #12', repo), []);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('ignores fenced code, like `Merge-After:` does', () => {
|
|
54
|
+
const body = ['```markdown', 'Closes #12', '```', 'Closes #13'].join('\n');
|
|
55
|
+
|
|
56
|
+
assert.deepStrictEqual(parseClosingIssueRefs(body, repo), [13]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('deduplicates and keeps the first occurrence order', () => {
|
|
60
|
+
assert.deepStrictEqual(
|
|
61
|
+
parseClosingIssueRefs('Closes #5\nFixes #3\nResolves #5', repo),
|
|
62
|
+
[5, 3],
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildReport,
|
|
3
|
+
type IssueDeclarations,
|
|
4
|
+
type OpenIssue,
|
|
5
|
+
type PrReport,
|
|
6
|
+
type PullRequestFacts,
|
|
7
|
+
type QueueComments,
|
|
8
|
+
type QueueOwner,
|
|
9
|
+
type RepoRef,
|
|
10
|
+
} from 'pr-report-core';
|
|
11
|
+
import { Arr, Result } from 'ts-data-forge';
|
|
12
|
+
import {
|
|
13
|
+
currentEpochMs,
|
|
14
|
+
isoSeconds,
|
|
15
|
+
resolveRepo,
|
|
16
|
+
tokenFromEnv,
|
|
17
|
+
} from '../lib/index.mjs';
|
|
18
|
+
import { createClient, type Client } from './github.mjs';
|
|
19
|
+
import type { Format, Options } from './options.mjs';
|
|
20
|
+
import { ownersOf } from './queue.mjs';
|
|
21
|
+
import { renderMarkdown, renderTerminal } from './render.mjs';
|
|
22
|
+
import { readRequiredContexts } from './repo-settings.mjs';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Reports the state of every open pull request, in one page.
|
|
26
|
+
*
|
|
27
|
+
* What it answers, and why each part is here rather than a click away on
|
|
28
|
+
* GitHub:
|
|
29
|
+
*
|
|
30
|
+
* - **The merge order.** The `Merge-After:` trailers and the stacks — a pull
|
|
31
|
+
* request onto another's branch — describe a graph that the pull request
|
|
32
|
+
* list cannot show at all; a stack of four reads as four unrelated rows
|
|
33
|
+
* there. Drawn as a tree, the one at the top is the one to look at.
|
|
34
|
+
* - **The issues each pull request closes**, so that "what is this for" does
|
|
35
|
+
* not need the body opened.
|
|
36
|
+
* - **The labels**, because `skip-ci` and `merge-queued` are how this
|
|
37
|
+
* repository says "not yet" and "ready", and neither means anything until
|
|
38
|
+
* someone reads them.
|
|
39
|
+
* - **The verdict of the checks the ruleset requires**, which is not the same
|
|
40
|
+
* as the checks that ran: a required context with nothing reported sits as
|
|
41
|
+
* "Expected — waiting" forever, and a red aggregate does not name what
|
|
42
|
+
* failed. Both are said here by name.
|
|
43
|
+
* - **How far ahead and behind the branch is.** A branch behind its base runs
|
|
44
|
+
* nothing and merges nothing, and the pull request page states it only as
|
|
45
|
+
* a sentence, without the size of the gap. For a stacked pull request the
|
|
46
|
+
* base is the layer below, and behind it means the layer below has moved
|
|
47
|
+
* and this one has not been restacked onto it.
|
|
48
|
+
* - **What landed recently.** The one section that is not about the queue,
|
|
49
|
+
* and the first question a reader of a daily report has: did the thing I
|
|
50
|
+
* queued yesterday go in.
|
|
51
|
+
* - **What is open that is not a pull request** — the open issues, so that
|
|
52
|
+
* "what is there to do" does not need a second tab.
|
|
53
|
+
* - **In the JSON only, whether `unblock-prs` set each one aside, who has
|
|
54
|
+
* claimed it** (`pr-report-core`'s `queue-comments.mts`), **and who moves
|
|
55
|
+
* it next** (`owner`: `worker`, `skill`, `human` or `none`; `queue.mts`),
|
|
56
|
+
* which is how a session working the queue picks what to fix.
|
|
57
|
+
*
|
|
58
|
+
* It reads and prints. Nothing here labels, rebases, merges or comments —
|
|
59
|
+
* that is `unblock-prs`, and keeping the report incapable of it is what makes
|
|
60
|
+
* it safe to run anywhere, on a schedule, with a read-only token or none.
|
|
61
|
+
*/
|
|
62
|
+
export const prReport = async (
|
|
63
|
+
options: Options,
|
|
64
|
+
): Promise<Result<string, string>> => {
|
|
65
|
+
const repo = await resolveRepo(options.repo);
|
|
66
|
+
|
|
67
|
+
if (Result.isErr(repo)) {
|
|
68
|
+
return repo;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const client = createClient(tokenFromEnv());
|
|
72
|
+
|
|
73
|
+
const defaultBranch = await client.defaultBranch(repo.value);
|
|
74
|
+
|
|
75
|
+
if (Result.isErr(defaultBranch)) {
|
|
76
|
+
return defaultBranch;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const required = await readRequiredContexts(
|
|
80
|
+
client,
|
|
81
|
+
repo.value,
|
|
82
|
+
defaultBranch.value,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (Result.isErr(required)) {
|
|
86
|
+
return required;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const facts = await client.facts(repo.value);
|
|
90
|
+
|
|
91
|
+
if (Result.isErr(facts)) {
|
|
92
|
+
return facts;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// One more request, and the one section that is not about what is open:
|
|
96
|
+
// what landed. A report that only lists the queue cannot answer "did the
|
|
97
|
+
// thing I queued yesterday go in", which is the first question a reader of
|
|
98
|
+
// a daily report has.
|
|
99
|
+
const merged = await client.merged(
|
|
100
|
+
repo.value,
|
|
101
|
+
options.mergedDays,
|
|
102
|
+
options.mergedLimit,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if (Result.isErr(merged)) {
|
|
106
|
+
return merged;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// And one more for what is open that is not a pull request, so that "what
|
|
110
|
+
// is there to do" is answered in one place rather than two.
|
|
111
|
+
const issues = await client.issues(repo.value, options.issuesLimit);
|
|
112
|
+
|
|
113
|
+
if (Result.isErr(issues)) {
|
|
114
|
+
return issues;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// And one request for each issue the open pull requests close that the
|
|
118
|
+
// open list did not cover, whose `Start-After:` the pull request closing
|
|
119
|
+
// it inherits as a merge order.
|
|
120
|
+
const issueDeclarations = await declarationsOf(
|
|
121
|
+
client,
|
|
122
|
+
repo.value,
|
|
123
|
+
facts.value,
|
|
124
|
+
issues.value,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// And one for each thing the issues' `Start-After:` names that is neither
|
|
128
|
+
// a listed issue nor an open pull request, to say where it stands.
|
|
129
|
+
const references = await client.references(
|
|
130
|
+
repo.value,
|
|
131
|
+
namedBeyond(facts.value, issues.value),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// Only the JSON carries these: they are what a session working the queue
|
|
135
|
+
// reads (`claude-plugin/skills/unblock-prs/SKILL.md`), not what a person does.
|
|
136
|
+
const queue =
|
|
137
|
+
options.format === 'json'
|
|
138
|
+
? await client.queueComments(repo.value, defaultBranch.value, facts.value)
|
|
139
|
+
: new Map<number, QueueComments>();
|
|
140
|
+
|
|
141
|
+
const report = buildReport({
|
|
142
|
+
repo: repo.value,
|
|
143
|
+
defaultBranch: defaultBranch.value,
|
|
144
|
+
generatedAt: isoSeconds(currentEpochMs()),
|
|
145
|
+
required: required.value,
|
|
146
|
+
authenticated: client.authenticated,
|
|
147
|
+
pulls: facts.value,
|
|
148
|
+
merged: merged.value,
|
|
149
|
+
mergedWithinDays: options.mergedDays,
|
|
150
|
+
issues: issues.value,
|
|
151
|
+
issuesLimit: options.issuesLimit,
|
|
152
|
+
issueDeclarations,
|
|
153
|
+
references,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const owners = ownersOf(report, queue, defaultBranch.value, currentEpochMs());
|
|
157
|
+
|
|
158
|
+
return Result.ok(print(report, queue, owners, options.format));
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* What the open issues listed, and the ones the open pull requests close
|
|
163
|
+
* beyond them, declare with `Start-After:`. A closing issue already known to
|
|
164
|
+
* be closed is not read: it closes nothing that is still to do.
|
|
165
|
+
*/
|
|
166
|
+
const declarationsOf = async (
|
|
167
|
+
client: Client,
|
|
168
|
+
repo: RepoRef,
|
|
169
|
+
pulls: readonly PullRequestFacts[],
|
|
170
|
+
issues: readonly OpenIssue[],
|
|
171
|
+
): Promise<IssueDeclarations> => {
|
|
172
|
+
const listed = new Set(issues.map(({ number }) => number));
|
|
173
|
+
|
|
174
|
+
const beyond = Arr.uniq(
|
|
175
|
+
pulls.flatMap(({ linkedIssues }) =>
|
|
176
|
+
linkedIssues
|
|
177
|
+
.filter(
|
|
178
|
+
({ number, state }) => state !== 'closed' && !listed.has(number),
|
|
179
|
+
)
|
|
180
|
+
.map(({ number }) => number),
|
|
181
|
+
),
|
|
182
|
+
).toSorted((a, b) => a - b);
|
|
183
|
+
|
|
184
|
+
const read = await client.issueBodies(repo, beyond);
|
|
185
|
+
|
|
186
|
+
const mut_startAfter = new Map(
|
|
187
|
+
issues.map(({ number, startAfter }) => [number, startAfter] as const),
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const mut_open = new Set(listed);
|
|
191
|
+
|
|
192
|
+
for (const [number, { open, startAfter }] of read) {
|
|
193
|
+
mut_startAfter.set(number, startAfter);
|
|
194
|
+
|
|
195
|
+
if (open) {
|
|
196
|
+
mut_open.add(number);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return { startAfter: mut_startAfter, open: mut_open };
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/** What the listed issues name that is neither listed nor an open pull request. */
|
|
204
|
+
const namedBeyond = (
|
|
205
|
+
pulls: readonly PullRequestFacts[],
|
|
206
|
+
issues: readonly OpenIssue[],
|
|
207
|
+
): readonly number[] => {
|
|
208
|
+
const known = new Set(
|
|
209
|
+
pulls
|
|
210
|
+
.map(({ number }) => number)
|
|
211
|
+
.concat(issues.map(({ number }) => number)),
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
return Arr.uniq(issues.flatMap(({ startAfter }) => startAfter))
|
|
215
|
+
.filter((number) => !known.has(number))
|
|
216
|
+
.toSorted((a, b) => a - b);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const print = (
|
|
220
|
+
report: PrReport,
|
|
221
|
+
queue: ReadonlyMap<number, QueueComments>,
|
|
222
|
+
owners: ReadonlyMap<number, QueueOwner>,
|
|
223
|
+
format: Format,
|
|
224
|
+
): string => {
|
|
225
|
+
switch (format) {
|
|
226
|
+
case 'json':
|
|
227
|
+
return JSON.stringify(serializable(report, queue, owners), undefined, 2);
|
|
228
|
+
|
|
229
|
+
case 'markdown':
|
|
230
|
+
return renderMarkdown(report);
|
|
231
|
+
|
|
232
|
+
case 'terminal':
|
|
233
|
+
return renderTerminal(report);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The report with its maps turned into objects, because `JSON.stringify`
|
|
239
|
+
* writes a `Map` as `{}` and would quietly drop every check verdict, and with
|
|
240
|
+
* what the queue's comments say on each entry (absent when there is none)
|
|
241
|
+
* and who moves it next (absent outside the queue, or when its comments
|
|
242
|
+
* could not be read; `queue.mts`).
|
|
243
|
+
*/
|
|
244
|
+
const serializable = (
|
|
245
|
+
report: PrReport,
|
|
246
|
+
queue: ReadonlyMap<number, QueueComments>,
|
|
247
|
+
owners: ReadonlyMap<number, QueueOwner>,
|
|
248
|
+
): unknown =>
|
|
249
|
+
({
|
|
250
|
+
...report,
|
|
251
|
+
entries: report.entries.map((entry) => ({
|
|
252
|
+
...entry,
|
|
253
|
+
reported: Object.fromEntries(entry.reported),
|
|
254
|
+
...queue.get(entry.number),
|
|
255
|
+
owner: owners.get(entry.number),
|
|
256
|
+
})),
|
|
257
|
+
}) as const;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/** What the command line says, and how it is read. */
|
|
2
|
+
|
|
3
|
+
import { Result } from 'ts-data-forge';
|
|
4
|
+
import { parseCommandArgs, positiveInteger } from '../lib/index.mjs';
|
|
5
|
+
|
|
6
|
+
export const FORMATS = ['json', 'markdown', 'terminal'] as const;
|
|
7
|
+
|
|
8
|
+
export type Format = (typeof FORMATS)[number];
|
|
9
|
+
|
|
10
|
+
export type Options = Readonly<{
|
|
11
|
+
format: Format;
|
|
12
|
+
/** `owner/name`; the repository this checkout is of when not given. */
|
|
13
|
+
repo: string | undefined;
|
|
14
|
+
/** How far back the "recently merged" section goes. */
|
|
15
|
+
mergedDays: number;
|
|
16
|
+
/** And how many of them it lists, whatever the window turns up. */
|
|
17
|
+
mergedLimit: number;
|
|
18
|
+
/** How many open issues it lists, most recently updated first. */
|
|
19
|
+
issuesLimit: number;
|
|
20
|
+
}>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A week, so that a report read on a Monday still covers the Friday. Long
|
|
24
|
+
* enough to answer "what landed since I last looked" and short enough that
|
|
25
|
+
* the section stays a list rather than an archive.
|
|
26
|
+
*/
|
|
27
|
+
export const DEFAULT_MERGED_DAYS = 7;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A cap as well as a window, and a matter of reading rather than of
|
|
31
|
+
* fitting. Twenty is more than enough to answer "did the thing I queued go in", which is what
|
|
32
|
+
* the section is for, and past that the section stops being a list and
|
|
33
|
+
* starts being an archive.
|
|
34
|
+
*/
|
|
35
|
+
export const DEFAULT_MERGED_LIMIT = 20;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Enough to see what is open without the section becoming the report. The
|
|
39
|
+
* page asks for the same number.
|
|
40
|
+
*/
|
|
41
|
+
export const DEFAULT_ISSUES_LIMIT = 30;
|
|
42
|
+
|
|
43
|
+
export const HELP = [
|
|
44
|
+
'Usage: pr-report [options]',
|
|
45
|
+
'',
|
|
46
|
+
'Reports every open pull request: the order they declare with',
|
|
47
|
+
'`Merge-After:`, the issues they close, their labels, the verdict of the',
|
|
48
|
+
'checks the ruleset requires, and how far each branch is from its base.',
|
|
49
|
+
'',
|
|
50
|
+
'Options:',
|
|
51
|
+
` --format <${FORMATS.join('|')}> how to print it (default terminal)`,
|
|
52
|
+
' `json` is the whole report, bodies and all',
|
|
53
|
+
' --repo <owner/name> which repository (default: this one)',
|
|
54
|
+
` --merged-days <n> how far back "recently merged" goes (default ${DEFAULT_MERGED_DAYS})`,
|
|
55
|
+
` --merged-limit <n> how many it lists at most (default ${DEFAULT_MERGED_LIMIT})`,
|
|
56
|
+
` --issues-limit <n> how many open issues it lists (default ${DEFAULT_ISSUES_LIMIT})`,
|
|
57
|
+
' -h, --help show this help',
|
|
58
|
+
'',
|
|
59
|
+
'Reads GH_TOKEN or GITHUB_TOKEN when one is set. Without it the public API',
|
|
60
|
+
'allows 60 requests an hour, which is about twenty pull requests, and the',
|
|
61
|
+
'issue links are the ones the bodies declare rather than GitHub’s own list.',
|
|
62
|
+
].join('\n');
|
|
63
|
+
|
|
64
|
+
export const parseOptions = (
|
|
65
|
+
args: readonly string[],
|
|
66
|
+
): Result<Options | 'help', string> => {
|
|
67
|
+
const parsed = parseCommandArgs(args, {
|
|
68
|
+
format: { type: 'string' },
|
|
69
|
+
repo: { type: 'string' },
|
|
70
|
+
'merged-days': { type: 'string' },
|
|
71
|
+
'merged-limit': { type: 'string' },
|
|
72
|
+
'issues-limit': { type: 'string' },
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (Result.isErr(parsed)) {
|
|
76
|
+
return parsed;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (parsed.value === 'help') {
|
|
80
|
+
return Result.ok('help');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const values = parsed.value;
|
|
84
|
+
|
|
85
|
+
const format = values.format ?? 'terminal';
|
|
86
|
+
|
|
87
|
+
if (!isFormat(format)) {
|
|
88
|
+
return Result.err(
|
|
89
|
+
`--format must be one of ${FORMATS.join(', ')}, got ${JSON.stringify(format)}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const mergedDays = positiveInteger(
|
|
94
|
+
'merged-days',
|
|
95
|
+
values['merged-days'],
|
|
96
|
+
DEFAULT_MERGED_DAYS,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (Result.isErr(mergedDays)) {
|
|
100
|
+
return mergedDays;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const mergedLimit = positiveInteger(
|
|
104
|
+
'merged-limit',
|
|
105
|
+
values['merged-limit'],
|
|
106
|
+
DEFAULT_MERGED_LIMIT,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (Result.isErr(mergedLimit)) {
|
|
110
|
+
return mergedLimit;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const issuesLimit = positiveInteger(
|
|
114
|
+
'issues-limit',
|
|
115
|
+
values['issues-limit'],
|
|
116
|
+
DEFAULT_ISSUES_LIMIT,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (Result.isErr(issuesLimit)) {
|
|
120
|
+
return issuesLimit;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return Result.ok({
|
|
124
|
+
format,
|
|
125
|
+
repo: values.repo,
|
|
126
|
+
mergedDays: mergedDays.value,
|
|
127
|
+
mergedLimit: mergedLimit.value,
|
|
128
|
+
issuesLimit: issuesLimit.value,
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Typed as a set of plain strings so that the membership test takes the
|
|
134
|
+
* string it is narrowing. `FORMATS.includes(value)` does not compile against
|
|
135
|
+
* the strict standard library, whose `includes` accepts only the element
|
|
136
|
+
* type — which is the very thing being established here.
|
|
137
|
+
*/
|
|
138
|
+
const FORMAT_SET: ReadonlySet<string> = new Set(FORMATS);
|
|
139
|
+
|
|
140
|
+
const isFormat = (value: string): value is Format => FORMAT_SET.has(value);
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildEntries,
|
|
3
|
+
queueOwnerOfEntry,
|
|
4
|
+
type DecidedEntries,
|
|
5
|
+
type QueueComments,
|
|
6
|
+
type QueueOwner,
|
|
7
|
+
type RepoRef,
|
|
8
|
+
} from 'pr-report-core';
|
|
9
|
+
import { Arr, Result } from 'ts-data-forge';
|
|
10
|
+
import { currentEpochMs, resolveRepo, tokenFromEnv } from '../lib/index.mjs';
|
|
11
|
+
import { createClient } from './github.mjs';
|
|
12
|
+
import { readRequiredContexts } from './repo-settings.mjs';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Who moves each open pull request in the queue next (`pr-report-core`'s
|
|
16
|
+
* `queue-owner.mts`), from what `pr-report` reads.
|
|
17
|
+
*
|
|
18
|
+
* Every command a session runs on the queue decides through here — the
|
|
19
|
+
* `owner` in `pr-report --format json`, `claim-pr`, `push-fixed-pr`,
|
|
20
|
+
* `rebuild-pnpm-update` and `report-unowned-prs` — so none of them can come
|
|
21
|
+
* to a different answer from the others, and the worker asks the same
|
|
22
|
+
* function with the same facts read through `gh`.
|
|
23
|
+
*/
|
|
24
|
+
export const ownersOf = (
|
|
25
|
+
decided: DecidedEntries,
|
|
26
|
+
queue: ReadonlyMap<number, QueueComments>,
|
|
27
|
+
defaultBranch: string,
|
|
28
|
+
nowEpochMs: number,
|
|
29
|
+
): ReadonlyMap<number, QueueOwner> =>
|
|
30
|
+
new Map(
|
|
31
|
+
Arr.generate(function* () {
|
|
32
|
+
for (const entry of decided.entries) {
|
|
33
|
+
// A pull request whose comments could not be read has no owner rather
|
|
34
|
+
// than the worker's: nothing is known of a set-aside on it.
|
|
35
|
+
const comments = queue.get(entry.number);
|
|
36
|
+
|
|
37
|
+
const owner =
|
|
38
|
+
comments === undefined
|
|
39
|
+
? undefined
|
|
40
|
+
: queueOwnerOfEntry(entry, comments, {
|
|
41
|
+
defaultBranch,
|
|
42
|
+
cycles: decided.cycles,
|
|
43
|
+
nowEpochMs,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (owner !== undefined) {
|
|
47
|
+
yield [entry.number, owner] as const;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
export type QueueState = Readonly<{
|
|
54
|
+
repo: RepoRef;
|
|
55
|
+
defaultBranch: string;
|
|
56
|
+
decided: DecidedEntries;
|
|
57
|
+
queue: ReadonlyMap<number, QueueComments>;
|
|
58
|
+
owners: ReadonlyMap<number, QueueOwner>;
|
|
59
|
+
}>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Reads the open pull requests, this account's comments on them and the
|
|
63
|
+
* owner of each, for a command that acts on the queue. It needs a token
|
|
64
|
+
* whose account is the one `unblock-prs` writes as: without one no comment
|
|
65
|
+
* is trusted, and there is no owner to read.
|
|
66
|
+
*/
|
|
67
|
+
export const readQueueState = async (
|
|
68
|
+
repoOption?: string,
|
|
69
|
+
): Promise<Result<QueueState, string>> => {
|
|
70
|
+
const repo = await resolveRepo(repoOption);
|
|
71
|
+
|
|
72
|
+
if (Result.isErr(repo)) {
|
|
73
|
+
return repo;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const client = createClient(tokenFromEnv());
|
|
77
|
+
|
|
78
|
+
if (!client.authenticated) {
|
|
79
|
+
return Result.err(
|
|
80
|
+
'no GH_TOKEN or GITHUB_TOKEN: the queue comments, and so the owners, cannot be read',
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const defaultBranch = await client.defaultBranch(repo.value);
|
|
85
|
+
|
|
86
|
+
if (Result.isErr(defaultBranch)) {
|
|
87
|
+
return defaultBranch;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const required = await readRequiredContexts(
|
|
91
|
+
client,
|
|
92
|
+
repo.value,
|
|
93
|
+
defaultBranch.value,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (Result.isErr(required)) {
|
|
97
|
+
return required;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const facts = await client.facts(repo.value);
|
|
101
|
+
|
|
102
|
+
if (Result.isErr(facts)) {
|
|
103
|
+
return facts;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const queue = await client.queueComments(
|
|
107
|
+
repo.value,
|
|
108
|
+
defaultBranch.value,
|
|
109
|
+
facts.value,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const decided = buildEntries({
|
|
113
|
+
required: required.value,
|
|
114
|
+
defaultBranch: defaultBranch.value,
|
|
115
|
+
pulls: facts.value,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return Result.ok({
|
|
119
|
+
repo: repo.value,
|
|
120
|
+
defaultBranch: defaultBranch.value,
|
|
121
|
+
decided,
|
|
122
|
+
queue,
|
|
123
|
+
owners: ownersOf(decided, queue, defaultBranch.value, currentEpochMs()),
|
|
124
|
+
});
|
|
125
|
+
};
|