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 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.1.0')
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prwatcher",
3
- "version": "2.1.0",
3
+ "version": "2.3.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,10 +187,29 @@ async function run(prUrl, options = {}) {
154
187
  return;
155
188
  }
156
189
 
157
- // Pending / other state
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
- console.log(` ⏳ ${timestamp} — CI running (${total - pending}/${total} required checks done, mergeable: ${state.mergeableState || 'unknown'})`);
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
  };