claudeup 4.27.0 → 4.27.1

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": "claudeup",
3
- "version": "4.27.0",
3
+ "version": "4.27.1",
4
4
  "description": "TUI tool for managing Claude Code plugins, MCPs, and configuration",
5
5
  "type": "module",
6
6
  "main": "src/main.tsx",
@@ -64,8 +64,8 @@
64
64
  "typescript": "^5.6.3"
65
65
  },
66
66
  "optionalDependencies": {
67
- "claudeup-darwin-arm64": "4.27.0",
68
- "claudeup-darwin-x64": "4.27.0",
69
- "claudeup-linux-x64": "4.27.0"
67
+ "claudeup-darwin-arm64": "4.27.1",
68
+ "claudeup-darwin-x64": "4.27.1",
69
+ "claudeup-linux-x64": "4.27.1"
70
70
  }
71
71
  }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Guards the update-check refresh fix. checkForUpdates caches within a session
3
+ * (so we don't hammer the registry), but `force` bypasses that cache so a
4
+ * release landing mid-session is actually seen — the 4.25-vs-4.26 banner
5
+ * staleness. (The fetch itself also sets `cache: "no-store"` so a CDN-stale
6
+ * response can't mask a fresh release.)
7
+ */
8
+ import { afterEach, describe, expect, it } from "bun:test";
9
+ import { checkForUpdates } from "../services/version-check.js";
10
+
11
+ describe("checkForUpdates — session cache vs forced re-check", () => {
12
+ const realFetch = globalThis.fetch;
13
+ afterEach(() => {
14
+ globalThis.fetch = realFetch;
15
+ });
16
+
17
+ it("serves the session cache by default and re-fetches only when forced", async () => {
18
+ let calls = 0;
19
+ let latest = "9.9.9";
20
+ globalThis.fetch = (async () => {
21
+ calls++;
22
+ return new Response(JSON.stringify({ version: latest }), {
23
+ status: 200,
24
+ headers: { "content-type": "application/json" },
25
+ });
26
+ }) as typeof fetch;
27
+
28
+ // Force the first call so the test is independent of any cache another
29
+ // test in the same process may have primed.
30
+ const first = await checkForUpdates(true);
31
+ expect(calls).toBe(1);
32
+ expect(first.latestVersion).toBe("9.9.9");
33
+
34
+ // Default call is served from the session cache — no new fetch.
35
+ const cached = await checkForUpdates();
36
+ expect(calls).toBe(1);
37
+ expect(cached.latestVersion).toBe("9.9.9");
38
+
39
+ // A release lands; a forced re-check bypasses the cache and sees it.
40
+ latest = "9.9.10";
41
+ const forced = await checkForUpdates(true);
42
+ expect(calls).toBe(2);
43
+ expect(forced.latestVersion).toBe("9.9.10");
44
+ });
45
+ });
@@ -33,6 +33,9 @@ async function fetchLatestVersion(): Promise<string | null> {
33
33
  signal: controller.signal,
34
34
  headers: {
35
35
  Accept: "application/json",
36
+ // Always revalidate — a CDN/HTTP-cached response serves a stale
37
+ // "latest" right after a release (the 4.25-vs-4.26 flicker).
38
+ "Cache-Control": "no-cache",
36
39
  },
37
40
  });
38
41
 
@@ -70,12 +73,15 @@ function getUpdateType(
70
73
  }
71
74
 
72
75
  /**
73
- * Check if a new version is available
74
- * Returns cached result if already checked this session
76
+ * Check if a new version is available.
77
+ * Returns the session-cached result unless `force` requests a fresh check.
75
78
  */
76
- export async function checkForUpdates(): Promise<VersionCheckResult> {
77
- // Return cached result if available
78
- if (cachedResult) {
79
+ export async function checkForUpdates(
80
+ force = false,
81
+ ): Promise<VersionCheckResult> {
82
+ // Return the session-cached result unless a fresh check is forced — a periodic
83
+ // re-check passes force:true so a release landing mid-session updates the banner.
84
+ if (!force && cachedResult) {
79
85
  return cachedResult;
80
86
  }
81
87
 
package/src/ui/App.tsx CHANGED
@@ -348,11 +348,22 @@ function AppContent({ onExit }: AppContentProps) {
348
348
  } | null>(null);
349
349
  const gitignoreModal = useGitignoreModal();
350
350
 
351
- // Check for updates on startup (non-blocking)
351
+ // Check for updates on startup, then re-check periodically (force-bypassing
352
+ // the session cache) so the banner reflects a release that lands while
353
+ // claudeup is left open — otherwise it shows the stale startup value.
352
354
  useEffect(() => {
353
355
  checkForUpdates()
354
356
  .then(setUpdateInfo)
355
357
  .catch(() => {});
358
+ const id = setInterval(
359
+ () => {
360
+ checkForUpdates(true)
361
+ .then(setUpdateInfo)
362
+ .catch(() => {});
363
+ },
364
+ 15 * 60 * 1000, // 15 min
365
+ );
366
+ return () => clearInterval(id);
356
367
  }, []);
357
368
 
358
369
  // Auto-dismiss recovery banner after 5 seconds