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,40 @@
|
|
|
1
|
+
import { Result } from 'ts-data-forge';
|
|
2
|
+
import { parseRepoRef, repoOfUrl } from './github-api.mjs';
|
|
3
|
+
|
|
4
|
+
describe(parseRepoRef, () => {
|
|
5
|
+
test('reads `owner/name`', () => {
|
|
6
|
+
assert.deepStrictEqual(
|
|
7
|
+
parseRepoRef('noshiro-pf/mono'),
|
|
8
|
+
Result.ok({ owner: 'noshiro-pf', name: 'mono' }),
|
|
9
|
+
);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test.each(['mono', 'noshiro-pf/', '/mono', 'a/b/c'])('refuses %j', (raw) => {
|
|
13
|
+
assert.deepStrictEqual(
|
|
14
|
+
parseRepoRef(raw),
|
|
15
|
+
Result.err(`expected owner/name, got ${JSON.stringify(raw)}`),
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe(repoOfUrl, () => {
|
|
21
|
+
test.each([
|
|
22
|
+
'https://github.com/noshiro-pf/mono',
|
|
23
|
+
'https://github.com/noshiro-pf/mono.git',
|
|
24
|
+
'git+https://github.com/noshiro-pf/mono.git',
|
|
25
|
+
'git@github.com:noshiro-pf/mono.git',
|
|
26
|
+
])('reads the repository out of %j', (url) => {
|
|
27
|
+
assert.deepStrictEqual(repoOfUrl(url), {
|
|
28
|
+
owner: 'noshiro-pf',
|
|
29
|
+
name: 'mono',
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test.each([
|
|
34
|
+
'http://local_proxy@127.0.0.1:41234/git/noshiro-pf/mono',
|
|
35
|
+
'https://gitlab.com/noshiro-pf/mono.git',
|
|
36
|
+
'',
|
|
37
|
+
])('names no GitHub repository in %j', (url) => {
|
|
38
|
+
assert.isUndefined(repoOfUrl(url));
|
|
39
|
+
});
|
|
40
|
+
});
|
package/src/lib/json.mts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Reading JSON into a schema's type, from text or from a file. */
|
|
2
|
+
|
|
3
|
+
import * as fs from 'node:fs/promises';
|
|
4
|
+
import { Json, Result } from 'ts-data-forge';
|
|
5
|
+
import * as t from 'ts-fortress';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* `text` parsed and validated against `schema`. `source` names where the text
|
|
9
|
+
* came from — a file, an API route, a command — so that the message says
|
|
10
|
+
* which answer was wrong, not only how.
|
|
11
|
+
*/
|
|
12
|
+
export const parseJson = <A,>(
|
|
13
|
+
text: string,
|
|
14
|
+
schema: t.Type<A>,
|
|
15
|
+
source: string,
|
|
16
|
+
): Result<A, string> => {
|
|
17
|
+
const parsed = Json.parse(text);
|
|
18
|
+
|
|
19
|
+
if (Result.isErr(parsed)) {
|
|
20
|
+
return Result.err(`${source} is not valid JSON: ${parsed.value}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const validated = schema.validate(parsed.value);
|
|
24
|
+
|
|
25
|
+
return Result.isErr(validated)
|
|
26
|
+
? Result.err(
|
|
27
|
+
`${source} has an unexpected shape:\n${t.validationErrorsToMessages(validated.value).join('\n')}`,
|
|
28
|
+
)
|
|
29
|
+
: Result.ok(validated.value);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** {@link parseJson} of a file's contents, the file naming itself. */
|
|
33
|
+
export const readJsonFile = async <A,>(
|
|
34
|
+
file: string,
|
|
35
|
+
schema: t.Type<A>,
|
|
36
|
+
): Promise<Result<A, string>> => {
|
|
37
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
38
|
+
const text = await Result.fromPromise(fs.readFile(file, 'utf8'));
|
|
39
|
+
|
|
40
|
+
return Result.isErr(text)
|
|
41
|
+
? Result.err(`cannot read ${file}`)
|
|
42
|
+
: parseJson(text.value, schema, file);
|
|
43
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Result } from 'ts-data-forge';
|
|
2
|
+
import * as t from 'ts-fortress';
|
|
3
|
+
import { parseJson } from './json.mjs';
|
|
4
|
+
|
|
5
|
+
describe(parseJson, () => {
|
|
6
|
+
const Schema = t.record({ number: t.number() });
|
|
7
|
+
|
|
8
|
+
test('answers the value when the text is JSON of the shape', () => {
|
|
9
|
+
assert.deepStrictEqual(
|
|
10
|
+
parseJson('{"number":1}', Schema, 'gh pr view 1'),
|
|
11
|
+
Result.ok({ number: 1 }),
|
|
12
|
+
);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('names the source when the text is not JSON', () => {
|
|
16
|
+
const parsed = parseJson('not json', Schema, 'gh pr view 1');
|
|
17
|
+
|
|
18
|
+
assert.isTrue(Result.isErr(parsed));
|
|
19
|
+
|
|
20
|
+
assert.isTrue(parsed.value.startsWith('gh pr view 1 is not valid JSON: '));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('names the source and the field when the shape is wrong', () => {
|
|
24
|
+
const parsed = parseJson('{"number":"1"}', Schema, 'gh pr view 1');
|
|
25
|
+
|
|
26
|
+
assert.isTrue(Result.isErr(parsed));
|
|
27
|
+
|
|
28
|
+
assert.isTrue(
|
|
29
|
+
parsed.value.startsWith('gh pr view 1 has an unexpected shape:\n'),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
assert.include(parsed.value, 'number');
|
|
33
|
+
});
|
|
34
|
+
});
|
package/src/lib/log.mts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** The log line every long-running command writes. */
|
|
2
|
+
|
|
3
|
+
import { currentEpochMs, localTimestamp } from './time.mjs';
|
|
4
|
+
|
|
5
|
+
export const log = (message: string): void => {
|
|
6
|
+
console.info(logLine(message));
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `message` stamped with the time in the machine's time zone (`TZ`), because
|
|
11
|
+
* the person reading the log reads it against their own clock. The offset
|
|
12
|
+
* stays in the stamp, so a line pasted elsewhere still names one instant.
|
|
13
|
+
* `offsetMinutes` (east of UTC) is the machine's unless given.
|
|
14
|
+
*/
|
|
15
|
+
export const logLine = (
|
|
16
|
+
message: string,
|
|
17
|
+
epochMs: number = currentEpochMs(),
|
|
18
|
+
offsetMinutes?: number,
|
|
19
|
+
): string => `[${localTimestamp(epochMs, offsetMinutes)}] ${message}` as const;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// cspell:ignore unstub
|
|
2
|
+
|
|
3
|
+
import { logLine } from './log.mjs';
|
|
4
|
+
|
|
5
|
+
describe(logLine, () => {
|
|
6
|
+
test('stamps the line with the local time and its offset from UTC', () => {
|
|
7
|
+
assert.strictEqual(
|
|
8
|
+
logLine('surveying', Date.parse('2026-09-27T12:03:04.500Z'), 9 * 60),
|
|
9
|
+
'[2026-09-27T21:03:04.500+09:00] surveying',
|
|
10
|
+
);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('writes the offset of a zone west of UTC, and of UTC itself', () => {
|
|
14
|
+
assert.strictEqual(
|
|
15
|
+
logLine('surveying', Date.parse('2026-09-27T12:03:04Z'), -4 * 60),
|
|
16
|
+
'[2026-09-27T08:03:04.000-04:00] surveying',
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
assert.strictEqual(
|
|
20
|
+
logLine('surveying', Date.parse('2026-09-27T12:03:04Z'), 0),
|
|
21
|
+
'[2026-09-27T12:03:04.000+00:00] surveying',
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('without a time given', () => {
|
|
26
|
+
test.each([
|
|
27
|
+
[
|
|
28
|
+
'Asia/Tokyo',
|
|
29
|
+
/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\+09:00\] surveying$/u,
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
'UTC',
|
|
33
|
+
/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\+00:00\] surveying$/u,
|
|
34
|
+
],
|
|
35
|
+
] as const)('uses the machine time zone (TZ=%s)', (tz, expected) => {
|
|
36
|
+
vi.stubEnv('TZ', tz);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
assert.match(logLine('surveying'), expected);
|
|
40
|
+
} finally {
|
|
41
|
+
vi.unstubAllEnvs();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Running `git`, `gh` and `pnpm` from a command, and quoting what they take. */
|
|
2
|
+
|
|
3
|
+
import { Result } from 'ts-data-forge';
|
|
4
|
+
import { $ } from 'ts-repo-utils';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Runs a git or gh command silently and resolves to its stdout, or to a
|
|
8
|
+
* message that includes what it printed to stderr. `unblock-prs` runs its
|
|
9
|
+
* `pnpm` commands through it too. It runs where the command was started
|
|
10
|
+
* unless told otherwise; git finds the repository from any directory in it.
|
|
11
|
+
*/
|
|
12
|
+
export const git = async (
|
|
13
|
+
command: string,
|
|
14
|
+
cwd: string = process.cwd(),
|
|
15
|
+
): Promise<Result<string, string>> => {
|
|
16
|
+
const result = await $(command, {
|
|
17
|
+
cwd,
|
|
18
|
+
silent: true,
|
|
19
|
+
env: childEnv,
|
|
20
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return Result.isOk(result)
|
|
24
|
+
? Result.ok(result.value.stdout)
|
|
25
|
+
: Result.err(result.value.message.trim());
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The root of the repository the command runs in, where its configuration
|
|
30
|
+
* files are read from: the working tree holding the current directory, or
|
|
31
|
+
* the current directory itself where git finds none (a tree without `.git`).
|
|
32
|
+
*/
|
|
33
|
+
export const repoRoot = async (): Promise<string> => {
|
|
34
|
+
const top = await git('git rev-parse --show-toplevel');
|
|
35
|
+
|
|
36
|
+
return Result.isOk(top) ? top.value.trim() : process.cwd();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Single-quotes a string for a POSIX shell. */
|
|
40
|
+
export const sh = (value: string): string =>
|
|
41
|
+
`'${value.replaceAll("'", String.raw`'\''`)}'` as const;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Passed to every git and gh invocation. `GIT_TERMINAL_PROMPT=0` turns a
|
|
45
|
+
* missing credential into a failure instead of a hang; `GIT_EDITOR=true`
|
|
46
|
+
* keeps any editor git might want out of the way.
|
|
47
|
+
*/
|
|
48
|
+
const childEnv: NodeJS.ProcessEnv = {
|
|
49
|
+
...process.env,
|
|
50
|
+
GIT_TERMINAL_PROMPT: '0',
|
|
51
|
+
GIT_EDITOR: 'true',
|
|
52
|
+
} as const;
|
package/src/lib/time.mts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The clock, and instants written as the commands write them — through
|
|
3
|
+
* `Date` rather than `Temporal`, which the oldest Node.js this package's
|
|
4
|
+
* `engines` names has only behind a flag. GitHub's
|
|
5
|
+
* timestamps are read with `pr-report-core`'s `epochMsOf`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Milliseconds since the epoch, now. */
|
|
9
|
+
export const currentEpochMs = (): number => Date.now();
|
|
10
|
+
|
|
11
|
+
/** An instant in UTC to the second, as GitHub writes one: `2026-09-29T01:02:03Z`. */
|
|
12
|
+
export const isoSeconds = (epochMs: number): string =>
|
|
13
|
+
`${isoMillis(epochMs).slice(0, 19)}Z` as const;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* An instant in local time to the millisecond, with its offset from UTC:
|
|
17
|
+
* `2026-09-27T21:03:04.500+09:00`. The offset is the machine's (`TZ`) at
|
|
18
|
+
* that instant unless one is given, in minutes east of UTC.
|
|
19
|
+
*/
|
|
20
|
+
export const localTimestamp = (
|
|
21
|
+
epochMs: number,
|
|
22
|
+
offsetMinutes: number = machineOffsetMinutes(epochMs),
|
|
23
|
+
): string => {
|
|
24
|
+
const local = isoMillis(epochMs + offsetMinutes * 60_000).slice(0, 23);
|
|
25
|
+
|
|
26
|
+
const sign = offsetMinutes < 0 ? '-' : '+';
|
|
27
|
+
|
|
28
|
+
const hours = Math.floor(Math.abs(offsetMinutes) / 60)
|
|
29
|
+
.toString()
|
|
30
|
+
.padStart(2, '0');
|
|
31
|
+
|
|
32
|
+
const minutes = (Math.abs(offsetMinutes) % 60).toString().padStart(2, '0');
|
|
33
|
+
|
|
34
|
+
return `${local}${sign}${hours}:${minutes}` as const;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** `2026-09-27T12:03:04.500Z`. */
|
|
38
|
+
const isoMillis = (epochMs: number): string => {
|
|
39
|
+
const date = new Date(epochMs);
|
|
40
|
+
|
|
41
|
+
return date.toISOString();
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** The machine's (`TZ`) offset from UTC at an instant, in minutes east. */
|
|
45
|
+
const machineOffsetMinutes = (epochMs: number): number => {
|
|
46
|
+
const date = new Date(epochMs);
|
|
47
|
+
|
|
48
|
+
return -date.getTimezoneOffset();
|
|
49
|
+
};
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
<!-- cspell:ignore unreviewed -->
|
|
2
|
+
|
|
3
|
+
# `open-pr`
|
|
4
|
+
|
|
5
|
+
`pnpm run open-pr` — opens the pull request for the current branch the way
|
|
6
|
+
this repository wants one opened: held by `skip-ci`, and never armed.
|
|
7
|
+
|
|
8
|
+
- [日本語](#日本語)
|
|
9
|
+
- [English](#english)
|
|
10
|
+
|
|
11
|
+
The conventions this follows are `CLAUDE.md`'s: "Commits and pull requests"
|
|
12
|
+
and "CI". This file describes what the script does with them.
|
|
13
|
+
|
|
14
|
+
## 日本語
|
|
15
|
+
|
|
16
|
+
### 何をするスクリプトか
|
|
17
|
+
|
|
18
|
+
現在のブランチを push し、PR を **ready for review** で作り、**`skip-ci`** と
|
|
19
|
+
**`auto-rebase`** を付けます。することはこれだけです。マージもせず、
|
|
20
|
+
`merge-queued` も付けず(それはレビュー後に人が行うことです)、**auto-merge も
|
|
21
|
+
張りません**。
|
|
22
|
+
|
|
23
|
+
### auto-merge を張らない理由
|
|
24
|
+
|
|
25
|
+
`main` の ruleset は `required_approving_review_count: 0` なので、
|
|
26
|
+
`.github/CODEOWNERS` が挙げるパスの外では、auto-merge の張られた PR は緑に
|
|
27
|
+
なった瞬間にマージされます。レビューが済んでいるかどうかは関係ありません。
|
|
28
|
+
stack する PR(下記)ならなおさらで、base は ruleset の掛からないブランチなので、
|
|
29
|
+
張った瞬間に下の層のブランチへマージされかねません。
|
|
30
|
+
|
|
31
|
+
そこで **auto-merge は `unblock-prs` が、`merge-queued` の付いた PR を pick した
|
|
32
|
+
ときに張ります**。キューに入れること( `merge-queued` )が「マージしてよい」の
|
|
33
|
+
合図で、それより前には誰も張りません。`skip-ci` はチェックを止め、
|
|
34
|
+
`no-skip-ci-label` でマージも止めておくためのものです。
|
|
35
|
+
|
|
36
|
+
### `auto-rebase` を付ける理由
|
|
37
|
+
|
|
38
|
+
`auto-rebase` は、レビューや順番を待っている間も `unblock-prs` に `main` へ追従
|
|
39
|
+
させるためのラベルです(`../unblock-prs/README.md`)。stack の
|
|
40
|
+
場合も全層に付けます。`unblock-prs` が rebase するのは base が `main` の PR だけ
|
|
41
|
+
なので、上の層に付いていても、下の層がマージされるまでは何も起きません。その
|
|
42
|
+
層が `main` に付け替えられたときに初めて効きますが、そのときに付ける仕組みは無
|
|
43
|
+
いので、最初から付けておきます。`skip-ci` の後に付けるのは、このラベルの
|
|
44
|
+
`labeled` で走る CI を `skip-ci` で止めるためです。
|
|
45
|
+
|
|
46
|
+
### 認証情報
|
|
47
|
+
|
|
48
|
+
GitHub の API を直接叩きます(`gh` は要りません)。トークンは
|
|
49
|
+
**`GH_TOKEN` → `GITHUB_TOKEN` → `gh auth token`** の順に探します(前2つは `gh` 自身が読む順で、`unblock-prs` などほかのコマンドと同じアカウントになるように揃えています)。コンテナや
|
|
50
|
+
runner には前2つが既にあり、`gh` で設定しただけのマシンでは3つ目が拾われるので、
|
|
51
|
+
どちらでも何も書き出さずに動きます。どれも無ければその旨を言って止まります。
|
|
52
|
+
|
|
53
|
+
`gh` を捨てたのは、このコマンドが一番効く場所 — クラウドのコンテナや runner —
|
|
54
|
+
が `gh` の入っていない場所だからです。呼ぶ API は `gh` と同じものです。
|
|
55
|
+
|
|
56
|
+
既存の PR が draft だったときに ready にする操作だけは REST に無いので、GraphQL
|
|
57
|
+
の mutation を使います。
|
|
58
|
+
|
|
59
|
+
**プロキシ配下でも動きます。** Node の `fetch` は `HTTPS_PROXY` を自動では見な
|
|
60
|
+
いため、スクリプト側で `NODE_USE_ENV_PROXY=1` を付けています。これが無いと
|
|
61
|
+
プロキシを迂回して `401 Bad credentials` になり、トークンの問題に見えます。
|
|
62
|
+
|
|
63
|
+
### 途中で失敗したら、もう一度実行してください
|
|
64
|
+
|
|
65
|
+
各ステップは既に済んでいればスキップされます。PR が既にあれば作らず、ラベルが
|
|
66
|
+
既にあれば付け直しません。巻き戻す必要はありません。
|
|
67
|
+
|
|
68
|
+
### 実行
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pnpm run open-pr # 既定
|
|
72
|
+
pnpm run open-pr -- --dry-run # 何をするかだけ表示
|
|
73
|
+
pnpm run open-pr -- --title 'fix: 説明' --body-file ./pr.md
|
|
74
|
+
pnpm run open-pr -- --merge-after '#1901' # 順序を宣言
|
|
75
|
+
pnpm run open-pr -- --base feat/lower-layer # その PR の上に積む
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
| オプション | 既定 | 意味 |
|
|
79
|
+
| :------------------- | ------------------: | :---------------------------------------- |
|
|
80
|
+
| `--title <text>` | 直前のコミット件名 | PR タイトル(英語、Conventional Commits) |
|
|
81
|
+
| `--body-file <path>` | トレーラのみ / 空欄 | 本文を読むファイル |
|
|
82
|
+
| `--base <branch>` | リポジトリの既定値 | 対象ブランチ(PR のブランチなら stack) |
|
|
83
|
+
| `--merge-after <n>` | なし | `Merge-After:` を宣言(複数回可) |
|
|
84
|
+
| `--dry-run` | off | 何も触らずに手順だけ出す |
|
|
85
|
+
|
|
86
|
+
**本文は自分で書くものです。** `skip-ci` が付いている間はローカルで走らせた
|
|
87
|
+
チェックがそのブランチの唯一のチェックなので、どれを走らせたかを本文に書いて
|
|
88
|
+
ください。`--merge-after` を付けると `Merge-After:` 行が本文の先頭に入ります。
|
|
89
|
+
|
|
90
|
+
**閉じる issue の `Start-After:` も本文に書きます。** PR を作った後、GitHub が
|
|
91
|
+
本文から読んだ closing keyword の issue を読み、その `Start-After:` が名指す
|
|
92
|
+
開いた PR(開いた issue なら、それを閉じる開いた PR。GitHub の issue の依存関係
|
|
93
|
+
blocked by も同じに読みます)のうち本文にまだ無いものを、
|
|
94
|
+
先頭の `Merge-After:` 行に足します(`docs/issue-start-after.md`)。`unblock-prs`
|
|
95
|
+
と各レポートは本文に無くても引き継ぐので、これは順序を本文にも書いておくための
|
|
96
|
+
ものです。サイドバーで手でリンクした issue は見ません。
|
|
97
|
+
|
|
98
|
+
### stacked PR
|
|
99
|
+
|
|
100
|
+
`--base` に別の open な PR のブランチを渡すと、その PR の上に積んだ PR になり
|
|
101
|
+
ます。差分はこの層の分だけになり、`unblock-prs` と各レポートは下の層がマージ
|
|
102
|
+
されるまで待つものとして扱います。下の層の番号は `Merge-After:` として本文の
|
|
103
|
+
先頭にも自動で入ります( `--merge-after` を併用すれば同じ行にまとめます)。
|
|
104
|
+
ツールは base だけでも stack を読むので、これは順序を本文にも書いておくための
|
|
105
|
+
ものです。
|
|
106
|
+
|
|
107
|
+
- その base を head とする open な PR がこのリポジトリにちょうど1本あること、
|
|
108
|
+
そして現在のブランチが `origin/<base>` の先端を含んでいることを、push の前に
|
|
109
|
+
確かめます。どちらかが違えば何もせずに止まります。
|
|
110
|
+
- auto-merge はほかの PR と同じく張りません。`unblock-prs` は下の層がマージされ
|
|
111
|
+
て GitHub がこの PR を既定ブランチに付け替えるまで pick しないので、張られる
|
|
112
|
+
のもそのときです。
|
|
113
|
+
- **GitHub のネイティブ stack にはしないでください。** stack は base だけで
|
|
114
|
+
作ります。GitHub はネイティブ stack に入った PR に auto-merge を張らせず、
|
|
115
|
+
下の層がマージされて既定ブランチに付け替えられた後も stack から外さないので、
|
|
116
|
+
`unblock-prs` はその PR を pick せず、手でマージするよう報告します。
|
|
117
|
+
|
|
118
|
+
### 拒否すること
|
|
119
|
+
|
|
120
|
+
- 既定ブランチの上にいるとき(先にブランチを切ってください)
|
|
121
|
+
- detached HEAD のとき
|
|
122
|
+
- 認証情報がどこからも取れないとき
|
|
123
|
+
- `--base` が open な PR のブランチでない、または現在のブランチがその先端を
|
|
124
|
+
含まないとき
|
|
125
|
+
|
|
126
|
+
既存の PR が draft だった場合は ready にします。`unblock-prs` は draft を pick
|
|
127
|
+
しないからです。
|
|
128
|
+
|
|
129
|
+
### この後
|
|
130
|
+
|
|
131
|
+
レビューが終わったら **`merge-queued` を付けるだけ**です。順番が来ると
|
|
132
|
+
`unblock-prs` が rebase して auto-merge を張り、`skip-ci` を外し、緑になった
|
|
133
|
+
時点で GitHub がマージします。**auto-merge を手で張らないでください** — それが
|
|
134
|
+
未レビューのままマージさせる操作です。
|
|
135
|
+
|
|
136
|
+
## English
|
|
137
|
+
|
|
138
|
+
`pnpm run open-pr` pushes the current branch, creates the pull request ready
|
|
139
|
+
for review, and adds `skip-ci` and `auto-rebase`. It merges nothing, never
|
|
140
|
+
adds `merge-queued`, which is a person's statement that the pull request has
|
|
141
|
+
been reviewed, and **never arms auto-merge**.
|
|
142
|
+
|
|
143
|
+
**Why it arms nothing.** The `main` ruleset asks for
|
|
144
|
+
`required_approving_review_count: 0`, so outside the paths `.github/CODEOWNERS`
|
|
145
|
+
lists an armed pull request merges the moment it goes green, reviewed or not.
|
|
146
|
+
A stacked one (below) is worse off: its base is a branch no ruleset covers, so
|
|
147
|
+
armed it could merge into the layer below at once. So **`unblock-prs` arms a
|
|
148
|
+
pull request when it picks it**, which it does only once it is labelled
|
|
149
|
+
`merge-queued` and onto the default branch: queueing is the signal that it may
|
|
150
|
+
merge, and nothing is armed before it. `skip-ci` holds the checks, and with
|
|
151
|
+
`no-skip-ci-label` the merge, until then.
|
|
152
|
+
|
|
153
|
+
**Why it adds `auto-rebase`.** The label asks `unblock-prs` to keep the pull
|
|
154
|
+
request on `main` while it waits for review or its turn
|
|
155
|
+
(`../unblock-prs/README.md`). It goes on every layer of a stack
|
|
156
|
+
too: `unblock-prs` rebases only a pull request based on `main`, so on a layer
|
|
157
|
+
above it does nothing until the layer below merges and GitHub moves this one
|
|
158
|
+
onto `main` — the moment it is needed, and one at which nothing would add it.
|
|
159
|
+
It goes on after `skip-ci`, so that the run its `labeled` event starts finds
|
|
160
|
+
the checks held.
|
|
161
|
+
|
|
162
|
+
It calls the GitHub API directly; `gh` is not required. The token is looked
|
|
163
|
+
for in **`GH_TOKEN`, then `GITHUB_TOKEN`, then `gh auth token`** — the order
|
|
164
|
+
`gh` itself reads the first two in, so that this acts as the account
|
|
165
|
+
`unblock-prs` does — the first
|
|
166
|
+
two are already set in a container or on a runner, and the third picks up a
|
|
167
|
+
machine set up with `gh` alone, so neither kind of machine has to export
|
|
168
|
+
anything. `gh` was dropped because the places this command is most useful are
|
|
169
|
+
the places `gh` is least likely to be installed; the API it calls is the same
|
|
170
|
+
one `gh` calls. Taking an existing pull request out of draft has no REST
|
|
171
|
+
equivalent and goes through GraphQL.
|
|
172
|
+
|
|
173
|
+
It works behind a proxy: Node's `fetch` does not read `HTTPS_PROXY` on its
|
|
174
|
+
own, so the script sets `NODE_USE_ENV_PROXY=1`. Without that it bypasses the
|
|
175
|
+
proxy and fails with `401 Bad credentials`, which reads like a token problem
|
|
176
|
+
and is not one.
|
|
177
|
+
|
|
178
|
+
Every step is skipped when it is already done, so a run that failed part way
|
|
179
|
+
through is finished by running it again rather than unpicked.
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
pnpm run open-pr # the default
|
|
183
|
+
pnpm run open-pr -- --dry-run # say what would be done
|
|
184
|
+
pnpm run open-pr -- --title 'fix: a thing' --body-file ./pr.md
|
|
185
|
+
pnpm run open-pr -- --merge-after '#1901' # declare an order
|
|
186
|
+
pnpm run open-pr -- --base feat/lower-layer # stack it on that one
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
| option | default | meaning |
|
|
190
|
+
| :------------------- | ---------------------: | :---------------------------------------- |
|
|
191
|
+
| `--title <text>` | last commit subject | the title, in English |
|
|
192
|
+
| `--body-file <path>` | the trailer, or empty | where to read the body from |
|
|
193
|
+
| `--base <branch>` | the repository default | what to target; a pull request's stacks |
|
|
194
|
+
| `--merge-after <n>` | none | declare a predecessor; repeat for several |
|
|
195
|
+
| `--dry-run` | off | print the steps and touch nothing |
|
|
196
|
+
|
|
197
|
+
The description is yours to write: while `skip-ci` is on, the checks you ran
|
|
198
|
+
locally are the only checks the branch gets, so say which ones. `--merge-after`
|
|
199
|
+
puts the `Merge-After:` line at the top of the body, where `unblock-prs` reads
|
|
200
|
+
it.
|
|
201
|
+
|
|
202
|
+
**What the issues it closes start after is written too.** Once the pull
|
|
203
|
+
request exists, the script reads the issues GitHub found closing keywords for
|
|
204
|
+
in the body, and adds to the `Merge-After:` line at the top the open pull
|
|
205
|
+
requests their `Start-After:` names — or, for an open issue named, the open
|
|
206
|
+
pull requests closing it; GitHub's "blocked by" counts the same — that the
|
|
207
|
+
body does not name yet
|
|
208
|
+
(`docs/issue-start-after.md`). `unblock-prs` and the reports inherit the same
|
|
209
|
+
without it; this is the body stating the order. An issue linked by hand in
|
|
210
|
+
the sidebar is not seen.
|
|
211
|
+
|
|
212
|
+
A `--base` that is another open pull request's branch stacks this one on it:
|
|
213
|
+
the diff is its own layer, and `unblock-prs` and the reports treat it as
|
|
214
|
+
waiting for the layer below. That layer is also written into the
|
|
215
|
+
`Merge-After:` line at the top of the body, first, alongside anything given
|
|
216
|
+
with `--merge-after`: the tools read the stack from the base alone, and the
|
|
217
|
+
trailer states the order in the body as every other declared order is. Before
|
|
218
|
+
pushing, the script checks that exactly one open pull request here is from
|
|
219
|
+
that branch and that the current branch contains the tip of `origin/<base>`,
|
|
220
|
+
and stops otherwise. `unblock-prs` does not pick it, and so does not arm it,
|
|
221
|
+
until the layer below has merged and GitHub has moved it onto the default
|
|
222
|
+
branch.
|
|
223
|
+
|
|
224
|
+
**Do not make it one of GitHub's native stacks**; the base alone is the
|
|
225
|
+
stack. GitHub refuses auto-merge on a pull request in a native stack, and
|
|
226
|
+
keeps it in the stack after the layer below has merged and it has been moved
|
|
227
|
+
onto the default branch, so `unblock-prs` does not pick it and reports it to
|
|
228
|
+
be merged by hand.
|
|
229
|
+
|
|
230
|
+
It refuses to run on the default branch, on a detached HEAD, and without a
|
|
231
|
+
credential. An existing pull request that is a draft is marked ready, because
|
|
232
|
+
`unblock-prs` does not pick a draft.
|
|
233
|
+
|
|
234
|
+
Afterwards, queueing it is one action: add `merge-queued` once it has been
|
|
235
|
+
reviewed. When its turn comes `unblock-prs` rebases it, arms auto-merge, takes
|
|
236
|
+
`skip-ci` off, and GitHub merges it once the checks are green. Arming it by
|
|
237
|
+
hand is the action that merges unreviewed work.
|