( packageName: string, current: string, fetchImpl: typeof fetch, timeoutMs = 8_000, )
| 28 | * Fetch registry package doc and compute update state. |
| 29 | */ |
| 30 | export async function computeUpdateCheck( |
| 31 | packageName: string, |
| 32 | current: string, |
| 33 | fetchImpl: typeof fetch, |
| 34 | timeoutMs = 8_000, |
| 35 | ): Promise<UpdateCheckResult | null> { |
| 36 | if (!semver.valid(current)) return null; |
| 37 | |
| 38 | const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`; |
| 39 | const resp = await fetchImpl(url, { signal: AbortSignal.timeout(timeoutMs) }); |
| 40 | if (!resp.ok) return null; |
| 41 | |
| 42 | const data = (await resp.json()) as { "dist-tags"?: Record<string, string> }; |
| 43 | const tags = data["dist-tags"] ?? {}; |
| 44 | const latestTag = tags.latest; |
| 45 | const betaTag = tags.beta; |
| 46 | |
| 47 | const onBeta = isPrerelease(current); |
| 48 | let updateAvailable = false; |
| 49 | let channel: "beta" | "latest" = "latest"; |
| 50 | let targetVersion = current; |
| 51 | let installCommand = `openclaw plugins install ${packageName}`; |
| 52 | |
| 53 | if (onBeta) { |
| 54 | channel = "beta"; |
| 55 | if (betaTag && semver.valid(betaTag) && semver.gt(betaTag, current)) { |
| 56 | updateAvailable = true; |
| 57 | targetVersion = betaTag; |
| 58 | } else { |
| 59 | targetVersion = betaTag && semver.valid(betaTag) ? betaTag : current; |
| 60 | } |
| 61 | installCommand = `openclaw plugins install ${packageName}@${targetVersion}`; |
| 62 | } else { |
| 63 | if (latestTag && semver.valid(latestTag) && semver.gt(latestTag, current)) { |
| 64 | updateAvailable = true; |
| 65 | targetVersion = latestTag; |
| 66 | } else { |
| 67 | targetVersion = latestTag && semver.valid(latestTag) ? latestTag : current; |
| 68 | } |
| 69 | installCommand = `openclaw plugins install ${packageName}@${targetVersion}`; |
| 70 | } |
| 71 | |
| 72 | // Beta user + stable exists on latest: optional hint to switch to stable (not counted as "update"). |
| 73 | let stableChannel: UpdateCheckResult["stableChannel"]; |
| 74 | if (onBeta && latestTag && semver.valid(latestTag) && !isPrerelease(latestTag)) { |
| 75 | stableChannel = { |
| 76 | version: latestTag, |
| 77 | installCommand: `openclaw plugins install ${packageName}@latest`, |
| 78 | }; |
| 79 | } |
| 80 | |
| 81 | return { |
| 82 | updateAvailable, |
| 83 | current, |
| 84 | latest: targetVersion, |
| 85 | packageName, |
| 86 | channel, |
| 87 | installCommand, |
no test coverage detected