newpr 0.5.5 → 0.5.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newpr",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "AI-powered large PR review tool - understand PRs with 1000+ lines of changes",
5
5
  "module": "src/cli/index.ts",
6
6
  "type": "module",
@@ -256,43 +256,43 @@ export function AppShell({
256
256
  </button>
257
257
  </div>
258
258
 
259
- {update.needsUpdate && (
259
+ {(update.needsUpdate || update.restarting) && (
260
260
  <div className="shrink-0 mx-2 mt-2 rounded-lg border bg-blue-500/5 px-3 py-2.5">
261
- <div className="flex items-center gap-2 mb-2">
262
- <Download className="h-3 w-3 text-blue-500 shrink-0" />
263
- <span className="text-[11px] font-medium text-blue-600 dark:text-blue-400">
264
- v{update.latest} available
265
- </span>
266
- </div>
267
- <button
268
- type="button"
269
- disabled={update.updating}
270
- onClick={update.doUpdate}
271
- className="w-full flex items-center justify-center gap-1.5 rounded-md bg-blue-500 hover:bg-blue-600 text-white text-[11px] font-medium py-1.5 transition-colors disabled:opacity-50"
272
- >
273
- {update.updating ? (
274
- <><Loader2 className="h-3 w-3 animate-spin" /> Updating...</>
275
- ) : (
276
- <><Download className="h-3 w-3" /> Update now</>
277
- )}
278
- </button>
279
- {update.error && (
280
- <p className="text-[10px] text-red-500 mt-1.5">{update.error}</p>
261
+ {update.restarting ? (
262
+ <div className="flex items-center gap-2">
263
+ <Loader2 className="h-3 w-3 animate-spin text-blue-500 shrink-0" />
264
+ <span className="text-[11px] text-blue-600 dark:text-blue-400">
265
+ Restarting...
266
+ </span>
267
+ </div>
268
+ ) : (
269
+ <>
270
+ <div className="flex items-center gap-2 mb-2">
271
+ <Download className="h-3 w-3 text-blue-500 shrink-0" />
272
+ <span className="text-[11px] font-medium text-blue-600 dark:text-blue-400">
273
+ v{update.latest} available
274
+ </span>
275
+ </div>
276
+ <button
277
+ type="button"
278
+ disabled={update.updating}
279
+ onClick={update.doUpdate}
280
+ className="w-full flex items-center justify-center gap-1.5 rounded-md bg-blue-500 hover:bg-blue-600 text-white text-[11px] font-medium py-1.5 transition-colors disabled:opacity-50"
281
+ >
282
+ {update.updating ? (
283
+ <><Loader2 className="h-3 w-3 animate-spin" /> Updating...</>
284
+ ) : (
285
+ <><Download className="h-3 w-3" /> Update &amp; restart</>
286
+ )}
287
+ </button>
288
+ {update.error && (
289
+ <p className="text-[10px] text-red-500 mt-1.5">{update.error}</p>
290
+ )}
291
+ </>
281
292
  )}
282
293
  </div>
283
294
  )}
284
295
 
285
- {update.updated && (
286
- <div className="shrink-0 mx-2 mt-2 rounded-lg border border-green-500/20 bg-green-500/5 px-3 py-2.5">
287
- <div className="flex items-center gap-2">
288
- <Check className="h-3 w-3 text-green-500 shrink-0" />
289
- <span className="text-[11px] text-green-600 dark:text-green-400">
290
- Updated! Restart newpr to apply.
291
- </span>
292
- </div>
293
- </div>
294
- )}
295
-
296
296
  <SessionList
297
297
  sessions={sessions}
298
298
  activeSessionId={activeSessionId}
@@ -6,10 +6,22 @@ interface UpdateState {
6
6
  current: string;
7
7
  latest: string;
8
8
  updating: boolean;
9
- updated: boolean;
9
+ restarting: boolean;
10
10
  error: string | null;
11
11
  }
12
12
 
13
+ async function waitForServer(maxWait = 30000): Promise<boolean> {
14
+ const start = Date.now();
15
+ while (Date.now() - start < maxWait) {
16
+ await new Promise((r) => setTimeout(r, 1000));
17
+ try {
18
+ const res = await fetch("/api/features", { signal: AbortSignal.timeout(2000) });
19
+ if (res.ok) return true;
20
+ } catch {}
21
+ }
22
+ return false;
23
+ }
24
+
13
25
  export function useUpdateCheck() {
14
26
  const [state, setState] = useState<UpdateState>({
15
27
  checking: true,
@@ -17,35 +29,49 @@ export function useUpdateCheck() {
17
29
  current: "",
18
30
  latest: "",
19
31
  updating: false,
20
- updated: false,
32
+ restarting: false,
21
33
  error: null,
22
34
  });
23
35
 
24
36
  useEffect(() => {
25
- fetch("/api/update-check")
26
- .then((r) => r.json())
27
- .then((data) => {
28
- const d = data as { current: string; latest: string; needsUpdate: boolean };
29
- setState((s) => ({
30
- ...s,
31
- checking: false,
32
- current: d.current,
33
- latest: d.latest,
34
- needsUpdate: d.needsUpdate,
35
- }));
36
- })
37
- .catch(() => setState((s) => ({ ...s, checking: false })));
37
+ const check = () => {
38
+ fetch("/api/update-check")
39
+ .then((r) => r.json())
40
+ .then((data) => {
41
+ const d = data as { current: string; latest: string; needsUpdate: boolean };
42
+ setState((s) => ({
43
+ ...s,
44
+ checking: false,
45
+ current: d.current,
46
+ latest: d.latest,
47
+ needsUpdate: s.restarting ? s.needsUpdate : d.needsUpdate,
48
+ }));
49
+ })
50
+ .catch(() => setState((s) => ({ ...s, checking: false })));
51
+ };
52
+ check();
53
+ const interval = setInterval(check, 60 * 60 * 1000);
54
+ return () => clearInterval(interval);
38
55
  }, []);
39
56
 
40
57
  const doUpdate = useCallback(async () => {
41
58
  setState((s) => ({ ...s, updating: true, error: null }));
42
59
  try {
43
60
  const res = await fetch("/api/update", { method: "POST" });
44
- const data = await res.json() as { ok: boolean; error?: string };
45
- if (data.ok) {
46
- setState((s) => ({ ...s, updating: false, updated: true, needsUpdate: false }));
47
- } else {
61
+ const data = await res.json() as { ok: boolean; restarting?: boolean; error?: string };
62
+ if (!data.ok) {
48
63
  setState((s) => ({ ...s, updating: false, error: data.error ?? "Update failed" }));
64
+ return;
65
+ }
66
+
67
+ if (data.restarting) {
68
+ setState((s) => ({ ...s, updating: false, restarting: true }));
69
+ const up = await waitForServer();
70
+ if (up) {
71
+ window.location.reload();
72
+ } else {
73
+ setState((s) => ({ ...s, restarting: false, error: "Server did not come back up. Try restarting manually." }));
74
+ }
49
75
  }
50
76
  } catch (err) {
51
77
  setState((s) => ({ ...s, updating: false, error: err instanceof Error ? err.message : String(err) }));
@@ -675,7 +675,18 @@ Before posting an inline comment, ALWAYS call \`get_file_diff\` first to find th
675
675
  if (exitCode !== 0) {
676
676
  return json({ ok: false, error: stderr.trim() || stdout.trim() }, 500);
677
677
  }
678
- return json({ ok: true, message: "Updated. Restart newpr to use the new version." });
678
+
679
+ setTimeout(() => {
680
+ Bun.spawn(["newpr", ...process.argv.slice(2)], {
681
+ cwd: process.cwd(),
682
+ stdin: "inherit",
683
+ stdout: "inherit",
684
+ stderr: "inherit",
685
+ });
686
+ setTimeout(() => process.exit(0), 500);
687
+ }, 1000);
688
+
689
+ return json({ ok: true, restarting: true });
679
690
  } catch (err) {
680
691
  return json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
681
692
  }