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 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.1.0')
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prwatcher",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "CLI tool that watches GitHub PRs and sends Slack notifications when CI passes, fails, or PR is ready to merge",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
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 (lastState !== 'rebase_needed') {
118
- console.log(` 🔄 ${timestamp}Needs rebase (${state.mergeableState})`);
119
- await sendNotification(config.slackWebhookUrl, formatRebaseNeeded(state.htmlUrl, state.title));
120
- lastState = 'rebase_needed';
121
- } else {
122
- console.log(` 🔄 ${timestamp} — Still needs rebase (already notified)`);
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
  };