Fix upgrade version detection (#11829)

This commit is contained in:
Chris Estreich 2026-03-02 09:43:40 -08:00 committed by GitHub
parent b00c260dc0
commit 5a4ab2b13f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 86 additions and 15 deletions

View file

@ -104,12 +104,60 @@ get_version() {
error "Failed to fetch releases from GitHub. Check your internet connection."
}
# Extract the latest cli-v* tag
VERSION=$(echo "$RELEASES_JSON" |
grep -o '"tag_name": "cli-v[^"]*"' |
head -1 |
sed 's/"tag_name": "cli-v//' |
sed 's/"//')
# Extract highest cli-v* tag by semantic version (do not rely on API ordering)
VERSION=$(printf "%s" "$RELEASES_JSON" | node -e '
const fs = require("fs")
const input = fs.readFileSync(0, "utf8")
let releases
try {
releases = JSON.parse(input)
} catch {
process.exit(1)
}
function parseVersion(version) {
const core = String(version).trim().split("+", 1)[0].split("-", 1)[0]
if (!core) return null
const parts = core.split(".")
if (parts.length === 0 || parts.some((part) => !/^\d+$/.test(part))) {
return null
}
return parts.map((part) => Number.parseInt(part, 10))
}
function compareVersions(a, b) {
const maxLength = Math.max(a.length, b.length)
for (let i = 0; i < maxLength; i++) {
const aPart = a[i] ?? 0
const bPart = b[i] ?? 0
if (aPart > bPart) return 1
if (aPart < bPart) return -1
}
return 0
}
let latestVersion = ""
let latestParts = null
if (Array.isArray(releases)) {
for (const release of releases) {
if (!release || typeof release.tag_name !== "string" || !release.tag_name.startsWith("cli-v")) {
continue
}
const candidate = release.tag_name.slice("cli-v".length)
const candidateParts = parseVersion(candidate)
if (!candidateParts) continue
if (!latestParts || compareVersions(candidateParts, latestParts) > 0) {
latestVersion = candidate
latestParts = candidateParts
}
}
}
if (latestVersion) {
process.stdout.write(latestVersion)
}
')
if [ -z "$VERSION" ]; then
error "Could not find any CLI releases. The CLI may not have been released yet."

View file

@ -26,18 +26,23 @@ describe("compareVersions", () => {
expect(compareVersions("cli-v1.2.3", "1.2.2")).toBe(1)
expect(compareVersions("1.2.3-beta.1", "1.2.3")).toBe(0)
})
it("compares multi-digit patch versions numerically", () => {
expect(compareVersions("0.1.10", "0.1.9")).toBe(1)
})
})
describe("getLatestCliVersion", () => {
it("returns the first cli-v release tag from GitHub releases", async () => {
it("returns the highest cli-v release tag from GitHub releases", async () => {
const fetchImpl = (async () =>
createFetchResponse([
{ tag_name: "cli-v0.1.9" },
{ tag_name: "v9.9.9" },
{ tag_name: "cli-v0.3.1" },
{ tag_name: "cli-v0.3.0" },
{ tag_name: "cli-v0.1.10" },
{ tag_name: "cli-v0.1.8" },
])) as typeof fetch
await expect(getLatestCliVersion(fetchImpl)).resolves.toBe("0.3.1")
await expect(getLatestCliVersion(fetchImpl)).resolves.toBe("0.1.10")
})
it("throws when release check fails", async () => {

View file

@ -82,6 +82,8 @@ export async function getLatestCliVersion(fetchImpl: typeof fetch = fetch): Prom
throw new Error("Invalid release response from GitHub.")
}
let latestVersion: string | undefined
for (const release of releases) {
if (!isRecord(release)) {
continue
@ -89,16 +91,28 @@ export async function getLatestCliVersion(fetchImpl: typeof fetch = fetch): Prom
const tagName = release.tag_name
if (typeof tagName === "string" && tagName.startsWith("cli-v")) {
return tagName.slice("cli-v".length)
const candidate = tagName.slice("cli-v".length)
try {
if (!latestVersion || compareVersions(candidate, latestVersion) > 0) {
latestVersion = candidate
}
} catch {
// Ignore malformed CLI tags and keep scanning other releases.
}
}
}
if (latestVersion) {
return latestVersion
}
throw new Error("Could not determine the latest CLI release version.")
}
export function runUpgradeInstaller(spawnImpl: typeof spawn = spawn): Promise<void> {
export function runUpgradeInstaller(version?: string, spawnImpl: typeof spawn = spawn): Promise<void> {
return new Promise((resolve, reject) => {
const child = spawnImpl("sh", ["-c", INSTALL_SCRIPT_COMMAND], { stdio: "inherit" })
const env = version ? { ...process.env, ROO_VERSION: version } : process.env
const child = spawnImpl("sh", ["-c", INSTALL_SCRIPT_COMMAND], { stdio: "inherit", env })
child.once("error", (error) => {
reject(error)
@ -119,7 +133,7 @@ export function runUpgradeInstaller(spawnImpl: typeof spawn = spawn): Promise<vo
export async function upgrade(options: UpgradeOptions = {}): Promise<void> {
const currentVersion = options.currentVersion ?? VERSION
const fetchImpl = options.fetchImpl ?? fetch
const runInstaller = options.runInstaller ?? (() => runUpgradeInstaller())
const runInstaller = options.runInstaller
console.log(`Current version: ${currentVersion}`)
@ -132,6 +146,10 @@ export async function upgrade(options: UpgradeOptions = {}): Promise<void> {
}
console.log(`Upgrading Roo CLI from ${currentVersion} to ${latestVersion}...`)
await runInstaller()
if (runInstaller) {
await runInstaller()
} else {
await runUpgradeInstaller(latestVersion)
}
console.log("✓ Upgrade completed.")
}