prwatcher 2.1.0 → 2.2.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/bin/watch-pr.js +2 -1
- package/package.json +1 -1
- package/src/cli.js +52 -7
- package/src/github.js +16 -1
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.2.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,6 +187,18 @@ async function run(prUrl, options = {}) {
|
|
|
154
187
|
return;
|
|
155
188
|
}
|
|
156
189
|
|
|
190
|
+
// Blocked — CI passed but review approval required
|
|
191
|
+
if (state.mergeableState === 'blocked') {
|
|
192
|
+
if (lastState !== 'needs_review') {
|
|
193
|
+
console.log(` 👀 ${timestamp} — Waiting for review approval`);
|
|
194
|
+
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.`);
|
|
195
|
+
lastState = 'needs_review';
|
|
196
|
+
} else {
|
|
197
|
+
console.log(` 👀 ${timestamp} — Still waiting for review (already notified)`);
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
157
202
|
// Pending / other state
|
|
158
203
|
const pending = state.checks ? state.checks.pendingRequired.length : 0;
|
|
159
204
|
const total = state.checks ? state.checks.requiredTotal : 0;
|
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
|
};
|