prwatcher 2.1.0 → 2.3.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/README.md +11 -2
- package/bin/watch-pr.js +2 -1
- package/package.json +1 -1
- package/src/cli.js +61 -9
- package/src/github.js +16 -1
package/README.md
CHANGED
|
@@ -41,14 +41,14 @@ On first run you'll be prompted for a GitHub personal access token. Here's what
|
|
|
41
41
|
- `Metadata` → Read-only (required)
|
|
42
42
|
- `Pull requests` → Read-only
|
|
43
43
|
|
|
44
|
-
#### Watching + Auto-merge (`--auto-merge`)
|
|
44
|
+
#### Watching + Auto-merge (`--auto-merge`) or Auto-rebase (`--auto-rebase`)
|
|
45
45
|
|
|
46
46
|
**Classic token** — check `repo` scope
|
|
47
47
|
|
|
48
48
|
**Fine-grained token** — enable:
|
|
49
49
|
- `Metadata` → Read-only (required)
|
|
50
50
|
- `Pull requests` → Read and write
|
|
51
|
-
- `Contents` → Read and write ← required for merging
|
|
51
|
+
- `Contents` → Read and write ← required for merging and rebasing
|
|
52
52
|
|
|
53
53
|
> Your token is saved to `~/.watch-pr` on your machine with owner-only permissions (0600). It is never sent anywhere except the GitHub API.
|
|
54
54
|
|
|
@@ -71,6 +71,7 @@ prwatcher <pr-url> [options]
|
|
|
71
71
|
Options:
|
|
72
72
|
--interval <minutes> Polling interval in minutes (default: 1)
|
|
73
73
|
--auto-merge Automatically merge PR once all required checks pass
|
|
74
|
+
--auto-rebase Automatically rebase PR branch when behind (no conflicts only)
|
|
74
75
|
--reset-config Re-enter GitHub token and Slack webhook URL
|
|
75
76
|
-V, --version Output version number
|
|
76
77
|
-h, --help Display help
|
|
@@ -85,6 +86,12 @@ prwatcher https://github.com/org/repo/pull/42
|
|
|
85
86
|
# Watch and auto-merge when ready
|
|
86
87
|
prwatcher https://github.com/org/repo/pull/42 --auto-merge
|
|
87
88
|
|
|
89
|
+
# Auto-rebase when behind base branch (no conflicts only)
|
|
90
|
+
prwatcher https://github.com/org/repo/pull/42 --auto-rebase
|
|
91
|
+
|
|
92
|
+
# Auto-rebase + auto-merge (full automation)
|
|
93
|
+
prwatcher https://github.com/org/repo/pull/42 --auto-rebase --auto-merge
|
|
94
|
+
|
|
88
95
|
# Poll every 30 seconds
|
|
89
96
|
prwatcher https://github.com/org/repo/pull/42 --interval 0.5
|
|
90
97
|
|
|
@@ -107,7 +114,9 @@ You'll get Slack messages when:
|
|
|
107
114
|
| PR is auto-merged | 🎉 PR was auto-merged! |
|
|
108
115
|
| PR is merged | 🎉 PR was merged! |
|
|
109
116
|
| PR is closed | ❌ PR was closed without merging |
|
|
117
|
+
| Waiting for review approval | 👀 PR waiting for review (CI passed) |
|
|
110
118
|
| Auto-merge failed | ⚠️ Auto-merge failed (with reason) |
|
|
119
|
+
| PR auto-rebased | 🔄 PR branch auto-rebased from base branch |
|
|
111
120
|
|
|
112
121
|
Notifications only fire on **state changes** — no spam.
|
|
113
122
|
|
package/bin/watch-pr.js
CHANGED
|
@@ -6,10 +6,11 @@ const { run } = require('../src/cli');
|
|
|
6
6
|
program
|
|
7
7
|
.name('prwatcher')
|
|
8
8
|
.description('Watch a GitHub PR and get Slack notifications on state changes')
|
|
9
|
-
.version('2.
|
|
9
|
+
.version('2.3.0')
|
|
10
10
|
.argument('<pr-url>', 'GitHub PR URL (e.g., https://github.com/owner/repo/pull/123)')
|
|
11
11
|
.option('--interval <minutes>', 'polling interval in minutes', '1')
|
|
12
12
|
.option('--auto-merge', 'automatically merge PR once all required checks pass')
|
|
13
|
+
.option('--auto-rebase', 'automatically rebase PR branch when it falls behind (no conflicts only)')
|
|
13
14
|
.option('--reset-config', 're-enter GitHub token and Slack webhook URL')
|
|
14
15
|
.action((prUrl, options) => {
|
|
15
16
|
run(prUrl, options);
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const { parsePRUrl, buildPRKey } = require('./utils');
|
|
2
2
|
const { ensureConfig } = require('./config');
|
|
3
|
-
const { createClient, fetchRequiredChecks, fetchPRState, mergePR } = require('./github');
|
|
3
|
+
const { createClient, fetchRequiredChecks, fetchPRState, mergePR, rebasePR } = require('./github');
|
|
4
4
|
const { sendNotification, formatMerged, formatClosed, formatCIFailed, formatRebaseNeeded, formatReady } = require('./slack');
|
|
5
5
|
|
|
6
6
|
async function run(prUrl, options = {}) {
|
|
@@ -73,6 +73,9 @@ async function run(prUrl, options = {}) {
|
|
|
73
73
|
if (options.autoMerge) {
|
|
74
74
|
console.log(' ⚠ Auto-merge is ON — PR will be merged automatically when ready');
|
|
75
75
|
}
|
|
76
|
+
if (options.autoRebase) {
|
|
77
|
+
console.log(' ⚠ Auto-rebase is ON — PR branch will be updated automatically when behind (no conflicts only)');
|
|
78
|
+
}
|
|
76
79
|
console.log(' Press Ctrl+C to stop\n');
|
|
77
80
|
|
|
78
81
|
// 6. Poll function
|
|
@@ -114,12 +117,42 @@ async function run(prUrl, options = {}) {
|
|
|
114
117
|
|
|
115
118
|
// Rebase needed
|
|
116
119
|
if (state.mergeableState === 'behind' || state.mergeableState === 'dirty') {
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
if (state.mergeableState === 'dirty') {
|
|
121
|
+
// Has conflicts — can't auto-rebase, just notify
|
|
122
|
+
if (lastState !== 'rebase_needed') {
|
|
123
|
+
console.log(` 🔄 ${timestamp} — Needs rebase (has conflicts, manual fix required)`);
|
|
124
|
+
await sendNotification(config.slackWebhookUrl, formatRebaseNeeded(state.htmlUrl, state.title));
|
|
125
|
+
lastState = 'rebase_needed';
|
|
126
|
+
} else {
|
|
127
|
+
console.log(` 🔄 ${timestamp} — Still has conflicts (already notified)`);
|
|
128
|
+
}
|
|
129
|
+
} else if (state.mergeableState === 'behind') {
|
|
130
|
+
if (options.autoRebase) {
|
|
131
|
+
// No conflicts — safe to auto-rebase
|
|
132
|
+
try {
|
|
133
|
+
console.log(` 🔄 ${timestamp} — Auto-rebasing from ${state.baseBranch}...`);
|
|
134
|
+
await rebasePR(octokit, { owner, repo, prNumber });
|
|
135
|
+
console.log(` ✅ ${timestamp} — Auto-rebased! Waiting for CI to re-run...`);
|
|
136
|
+
await sendNotification(config.slackWebhookUrl, `🔄 *PR branch auto-rebased from ${state.baseBranch}*\n<${state.htmlUrl}|${state.title}>\nCI will re-run on the updated branch.`);
|
|
137
|
+
lastState = null; // reset so we notify again after CI re-runs
|
|
138
|
+
} catch (rebaseError) {
|
|
139
|
+
const rebaseMsg = rebaseError.response?.data?.message || rebaseError.message;
|
|
140
|
+
const hint = rebaseError.status === 403
|
|
141
|
+
? ' (GitHub token needs "Contents: Read and write" permission)'
|
|
142
|
+
: '';
|
|
143
|
+
console.error(` ⚠ ${timestamp} — Auto-rebase failed: ${rebaseMsg}${hint}`);
|
|
144
|
+
await sendNotification(config.slackWebhookUrl, formatRebaseNeeded(state.htmlUrl, state.title));
|
|
145
|
+
lastState = 'rebase_needed';
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
if (lastState !== 'rebase_needed') {
|
|
149
|
+
console.log(` 🔄 ${timestamp} — Needs rebase (behind ${state.baseBranch})`);
|
|
150
|
+
await sendNotification(config.slackWebhookUrl, formatRebaseNeeded(state.htmlUrl, state.title));
|
|
151
|
+
lastState = 'rebase_needed';
|
|
152
|
+
} else {
|
|
153
|
+
console.log(` 🔄 ${timestamp} — Still behind (already notified)`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
123
156
|
}
|
|
124
157
|
return;
|
|
125
158
|
}
|
|
@@ -154,10 +187,29 @@ async function run(prUrl, options = {}) {
|
|
|
154
187
|
return;
|
|
155
188
|
}
|
|
156
189
|
|
|
157
|
-
//
|
|
190
|
+
// Check if CI is still running — show pending status regardless of mergeable state
|
|
158
191
|
const pending = state.checks ? state.checks.pendingRequired.length : 0;
|
|
159
192
|
const total = state.checks ? state.checks.requiredTotal : 0;
|
|
160
|
-
|
|
193
|
+
|
|
194
|
+
if (pending > 0) {
|
|
195
|
+
console.log(` ⏳ ${timestamp} — CI running (${total - pending}/${total} required checks done)`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Blocked — all CI passed but review approval or other requirement missing
|
|
200
|
+
if (state.mergeableState === 'blocked') {
|
|
201
|
+
if (lastState !== 'needs_review') {
|
|
202
|
+
console.log(` 👀 ${timestamp} — Waiting for review approval`);
|
|
203
|
+
await sendNotification(config.slackWebhookUrl, `👀 *PR is waiting for review approval*\n<${state.htmlUrl}|${state.title}>\nAll CI checks passed — needs at least 1 approving review to merge.`);
|
|
204
|
+
lastState = 'needs_review';
|
|
205
|
+
} else {
|
|
206
|
+
console.log(` 👀 ${timestamp} — Still waiting for review (already notified)`);
|
|
207
|
+
}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Other unknown state
|
|
212
|
+
console.log(` ⏳ ${timestamp} — Waiting (mergeable: ${state.mergeableState || 'unknown'})`);
|
|
161
213
|
|
|
162
214
|
} catch (error) {
|
|
163
215
|
const timestamp = new Date().toLocaleTimeString();
|
package/src/github.js
CHANGED
|
@@ -129,9 +129,24 @@ async function mergePR(octokit, { owner, repo, prNumber }) {
|
|
|
129
129
|
return data;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Update (rebase) a PR branch with the latest from its base branch.
|
|
134
|
+
* Only works when mergeable_state is 'behind' (no conflicts).
|
|
135
|
+
* Throws if there are conflicts or insufficient permissions.
|
|
136
|
+
*/
|
|
137
|
+
async function rebasePR(octokit, { owner, repo, prNumber }) {
|
|
138
|
+
const { data } = await octokit.pulls.updateBranch({
|
|
139
|
+
owner,
|
|
140
|
+
repo,
|
|
141
|
+
pull_number: prNumber
|
|
142
|
+
});
|
|
143
|
+
return data;
|
|
144
|
+
}
|
|
145
|
+
|
|
132
146
|
module.exports = {
|
|
133
147
|
createClient,
|
|
134
148
|
fetchRequiredChecks,
|
|
135
149
|
fetchPRState,
|
|
136
|
-
mergePR
|
|
150
|
+
mergePR,
|
|
151
|
+
rebasePR
|
|
137
152
|
};
|