Skip to content

Commit 612e9b3

Browse files
fix(auto-update): implement channel-based version fetching
Add support for npm dist-tag channels (@beta, @next, @canary) in auto-update mechanism. Users pinned to oh-my-opencode@beta now correctly fetch and compare against beta channel instead of stable latest. - Add extractChannel() to detect channel from version string - Modify getLatestVersion() to accept channel parameter - Update auto-update flow to use channel-aware fetching - Add comprehensive tests for channel detection and fetching - Resolves #687 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <[email protected]>
1 parent f27e93b commit 612e9b3

File tree

4 files changed

+161
-29
lines changed

4 files changed

+161
-29
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { describe, test, expect } from "bun:test"
2+
import { getLatestVersion } from "./checker"
3+
4+
describe("auto-update-checker/checker", () => {
5+
describe("getLatestVersion", () => {
6+
test("accepts channel parameter", async () => {
7+
const result = await getLatestVersion("beta")
8+
9+
expect(typeof result === "string" || result === null).toBe(true)
10+
})
11+
12+
test("accepts latest channel", async () => {
13+
const result = await getLatestVersion("latest")
14+
15+
expect(typeof result === "string" || result === null).toBe(true)
16+
})
17+
18+
test("works without channel (defaults to latest)", async () => {
19+
const result = await getLatestVersion()
20+
21+
expect(typeof result === "string" || result === null).toBe(true)
22+
})
23+
})
24+
})

src/hooks/auto-update-checker/checker.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ export function updatePinnedVersion(configPath: string, oldEntry: string, newVer
231231
}
232232
}
233233

234-
export async function getLatestVersion(): Promise<string | null> {
234+
export async function getLatestVersion(channel: string = "latest"): Promise<string | null> {
235235
const controller = new AbortController()
236236
const timeoutId = setTimeout(() => controller.abort(), NPM_FETCH_TIMEOUT)
237237

@@ -244,7 +244,7 @@ export async function getLatestVersion(): Promise<string | null> {
244244
if (!response.ok) return null
245245

246246
const data = (await response.json()) as NpmDistTags
247-
return data.latest ?? null
247+
return data[channel] ?? data.latest ?? null
248248
} catch {
249249
return null
250250
} finally {
@@ -264,24 +264,21 @@ export async function checkForUpdate(directory: string): Promise<UpdateCheckResu
264264
return { needsUpdate: false, currentVersion: null, latestVersion: null, isLocalDev: false, isPinned: false }
265265
}
266266

267-
if (pluginInfo.isPinned) {
268-
log(`[auto-update-checker] Version pinned to ${pluginInfo.pinnedVersion}, skipping update check`)
269-
return { needsUpdate: false, currentVersion: pluginInfo.pinnedVersion, latestVersion: null, isLocalDev: false, isPinned: true }
270-
}
271-
272-
const currentVersion = getCachedVersion()
267+
const currentVersion = getCachedVersion() ?? pluginInfo.pinnedVersion
273268
if (!currentVersion) {
274269
log("[auto-update-checker] No cached version found")
275270
return { needsUpdate: false, currentVersion: null, latestVersion: null, isLocalDev: false, isPinned: false }
276271
}
277272

278-
const latestVersion = await getLatestVersion()
273+
const { extractChannel } = await import("./index")
274+
const channel = extractChannel(pluginInfo.pinnedVersion ?? currentVersion)
275+
const latestVersion = await getLatestVersion(channel)
279276
if (!latestVersion) {
280-
log("[auto-update-checker] Failed to fetch latest version")
281-
return { needsUpdate: false, currentVersion, latestVersion: null, isLocalDev: false, isPinned: false }
277+
log("[auto-update-checker] Failed to fetch latest version for channel:", channel)
278+
return { needsUpdate: false, currentVersion, latestVersion: null, isLocalDev: false, isPinned: pluginInfo.isPinned }
282279
}
283280

284281
const needsUpdate = currentVersion !== latestVersion
285-
log(`[auto-update-checker] Current: ${currentVersion}, Latest: ${latestVersion}, NeedsUpdate: ${needsUpdate}`)
286-
return { needsUpdate, currentVersion, latestVersion, isLocalDev: false, isPinned: false }
282+
log(`[auto-update-checker] Current: ${currentVersion}, Latest (${channel}): ${latestVersion}, NeedsUpdate: ${needsUpdate}`)
283+
return { needsUpdate, currentVersion, latestVersion, isLocalDev: false, isPinned: pluginInfo.isPinned }
287284
}

src/hooks/auto-update-checker/index.test.ts

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, test, expect } from "bun:test"
2-
import { isPrereleaseVersion, isDistTag, isPrereleaseOrDistTag } from "./index"
2+
import { isPrereleaseVersion, isDistTag, isPrereleaseOrDistTag, extractChannel } from "./index"
33

44
describe("auto-update-checker", () => {
55
describe("isPrereleaseVersion", () => {
@@ -150,4 +150,105 @@ describe("auto-update-checker", () => {
150150
expect(result).toBe(false)
151151
})
152152
})
153+
154+
describe("extractChannel", () => {
155+
test("extracts beta from dist-tag", () => {
156+
// #given beta dist-tag
157+
const version = "beta"
158+
159+
// #when extracting channel
160+
const result = extractChannel(version)
161+
162+
// #then returns beta
163+
expect(result).toBe("beta")
164+
})
165+
166+
test("extracts next from dist-tag", () => {
167+
// #given next dist-tag
168+
const version = "next"
169+
170+
// #when extracting channel
171+
const result = extractChannel(version)
172+
173+
// #then returns next
174+
expect(result).toBe("next")
175+
})
176+
177+
test("extracts canary from dist-tag", () => {
178+
// #given canary dist-tag
179+
const version = "canary"
180+
181+
// #when extracting channel
182+
const result = extractChannel(version)
183+
184+
// #then returns canary
185+
expect(result).toBe("canary")
186+
})
187+
188+
test("extracts beta from prerelease version", () => {
189+
// #given beta prerelease version
190+
const version = "3.0.0-beta.1"
191+
192+
// #when extracting channel
193+
const result = extractChannel(version)
194+
195+
// #then returns beta
196+
expect(result).toBe("beta")
197+
})
198+
199+
test("extracts alpha from prerelease version", () => {
200+
// #given alpha prerelease version
201+
const version = "1.0.0-alpha"
202+
203+
// #when extracting channel
204+
const result = extractChannel(version)
205+
206+
// #then returns alpha
207+
expect(result).toBe("alpha")
208+
})
209+
210+
test("extracts rc from prerelease version", () => {
211+
// #given rc prerelease version
212+
const version = "2.0.0-rc.1"
213+
214+
// #when extracting channel
215+
const result = extractChannel(version)
216+
217+
// #then returns rc
218+
expect(result).toBe("rc")
219+
})
220+
221+
test("returns latest for stable version", () => {
222+
// #given stable version
223+
const version = "2.14.0"
224+
225+
// #when extracting channel
226+
const result = extractChannel(version)
227+
228+
// #then returns latest
229+
expect(result).toBe("latest")
230+
})
231+
232+
test("returns latest for null", () => {
233+
// #given null version
234+
const version = null
235+
236+
// #when extracting channel
237+
const result = extractChannel(version)
238+
239+
// #then returns latest
240+
expect(result).toBe("latest")
241+
})
242+
243+
test("handles complex prerelease identifiers", () => {
244+
// #given complex prerelease
245+
const version = "3.0.0-beta.1.experimental"
246+
247+
// #when extracting channel
248+
const result = extractChannel(version)
249+
250+
// #then returns beta
251+
expect(result).toBe("beta")
252+
})
253+
})
153254
})

src/hooks/auto-update-checker/index.ts

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,26 @@ export function isPrereleaseOrDistTag(pinnedVersion: string | null): boolean {
2323
return isPrereleaseVersion(pinnedVersion) || isDistTag(pinnedVersion)
2424
}
2525

26+
export function extractChannel(version: string | null): string {
27+
if (!version) return "latest"
28+
29+
if (isDistTag(version)) {
30+
return version
31+
}
32+
33+
if (isPrereleaseVersion(version)) {
34+
const prereleasePart = version.split("-")[1]
35+
if (prereleasePart) {
36+
const channelMatch = prereleasePart.match(/^(alpha|beta|rc|canary|next)/)
37+
if (channelMatch) {
38+
return channelMatch[1]
39+
}
40+
}
41+
}
42+
43+
return "latest"
44+
}
45+
2646
export function createAutoUpdateCheckerHook(ctx: PluginInput, options: AutoUpdateCheckerOptions = {}) {
2747
const { showStartupToast = true, isSisyphusEnabled = false, autoUpdate = true } = options
2848

@@ -94,37 +114,27 @@ async function runBackgroundUpdateCheck(
94114
return
95115
}
96116

97-
const latestVersion = await getLatestVersion()
117+
const channel = extractChannel(pluginInfo.pinnedVersion ?? currentVersion)
118+
const latestVersion = await getLatestVersion(channel)
98119
if (!latestVersion) {
99-
log("[auto-update-checker] Failed to fetch latest version")
120+
log("[auto-update-checker] Failed to fetch latest version for channel:", channel)
100121
return
101122
}
102123

103124
if (currentVersion === latestVersion) {
104-
log("[auto-update-checker] Already on latest version")
125+
log("[auto-update-checker] Already on latest version for channel:", channel)
105126
return
106127
}
107128

108-
log(`[auto-update-checker] Update available: ${currentVersion}${latestVersion}`)
129+
log(`[auto-update-checker] Update available (${channel}): ${currentVersion}${latestVersion}`)
109130

110131
if (!autoUpdate) {
111132
await showUpdateAvailableToast(ctx, latestVersion, getToastMessage)
112133
log("[auto-update-checker] Auto-update disabled, notification only")
113134
return
114135
}
115136

116-
// Check if current version is a prerelease - don't auto-downgrade prerelease to stable
117-
if (isPrereleaseVersion(currentVersion)) {
118-
log(`[auto-update-checker] Skipping auto-update for prerelease version: ${currentVersion}`)
119-
return
120-
}
121-
122137
if (pluginInfo.isPinned) {
123-
if (isPrereleaseOrDistTag(pluginInfo.pinnedVersion)) {
124-
log(`[auto-update-checker] Skipping auto-update for prerelease/dist-tag: ${pluginInfo.pinnedVersion}`)
125-
return
126-
}
127-
128138
const updated = updatePinnedVersion(pluginInfo.configPath, pluginInfo.entry, latestVersion)
129139
if (!updated) {
130140
await showUpdateAvailableToast(ctx, latestVersion, getToastMessage)

0 commit comments

Comments
 (0)