mirror of
https://github.com/cline/cline.git
synced 2026-09-24 23:20:16 +08:00
fix(cli): support auto-updates for nightly versions (#9034)
* fix(cli): support auto-updates for nightly versions Previously, the auto-update logic only checked npm's "latest" tag, so users on nightly builds (2.0.0-nightly.X) would never receive nightly updates. The update commands also hardcoded @latest. Changes: - Detect nightly versions by checking for "-nightly." in version string - Query npm "nightly" tag when current version is a nightly build - Use @nightly in update commands for nightly users - Fix compareVersions() to properly parse and compare nightly timestamps (previously it would produce NaN when parsing "2.0.0-nightly.X") * fix: tighten nightly version regex to require valid semver format
This commit is contained in:
+77
-18
@@ -19,9 +19,26 @@ interface InstallationInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect how the CLI was installed and return the appropriate update command.
|
||||
* Check if a version string is a nightly build.
|
||||
*/
|
||||
function getInstallationInfo(): InstallationInfo {
|
||||
function isNightlyVersion(version: string): boolean {
|
||||
return version.includes("-nightly.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the npm tag to use based on the current version.
|
||||
*/
|
||||
function getNpmTag(currentVersion: string): string {
|
||||
return isNightlyVersion(currentVersion) ? "nightly" : "latest"
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect how the CLI was installed and return the appropriate update command.
|
||||
* Uses the correct npm tag based on whether the current version is nightly.
|
||||
*/
|
||||
function getInstallationInfo(currentVersion: string): InstallationInfo {
|
||||
const tag = getNpmTag(currentVersion)
|
||||
|
||||
try {
|
||||
const scriptPath = realpathSync(process.argv[1] || "").replace(/\\/g, "/")
|
||||
|
||||
@@ -34,7 +51,7 @@ function getInstallationInfo(): InstallationInfo {
|
||||
if (scriptPath.includes("/.pnpm/global") || scriptPath.includes("/pnpm/global")) {
|
||||
return {
|
||||
packageManager: PackageManager.PNPM,
|
||||
updateCommand: "pnpm add -g cline@latest",
|
||||
updateCommand: `pnpm add -g cline@${tag}`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +59,7 @@ function getInstallationInfo(): InstallationInfo {
|
||||
if (scriptPath.includes("/.yarn/") || scriptPath.includes("/yarn/global")) {
|
||||
return {
|
||||
packageManager: PackageManager.YARN,
|
||||
updateCommand: "yarn global add cline@latest",
|
||||
updateCommand: `yarn global add cline@${tag}`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +67,7 @@ function getInstallationInfo(): InstallationInfo {
|
||||
if (scriptPath.includes("/.bun/bin")) {
|
||||
return {
|
||||
packageManager: PackageManager.BUN,
|
||||
updateCommand: "bun add -g cline@latest",
|
||||
updateCommand: `bun add -g cline@${tag}`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +75,7 @@ function getInstallationInfo(): InstallationInfo {
|
||||
if (scriptPath.includes("/node_modules/cline/")) {
|
||||
return {
|
||||
packageManager: PackageManager.NPM,
|
||||
updateCommand: "npm install -g cline@latest",
|
||||
updateCommand: `npm install -g cline@${tag}`,
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -70,10 +87,12 @@ function getInstallationInfo(): InstallationInfo {
|
||||
|
||||
/**
|
||||
* Fetch the latest version from npm registry.
|
||||
* Uses the appropriate tag based on whether the current version is nightly.
|
||||
*/
|
||||
async function getLatestVersion(): Promise<string | null> {
|
||||
async function getLatestVersion(currentVersion: string): Promise<string | null> {
|
||||
try {
|
||||
const response = await fetch("https://registry.npmjs.org/cline/latest")
|
||||
const tag = getNpmTag(currentVersion)
|
||||
const response = await fetch(`https://registry.npmjs.org/cline/${tag}`)
|
||||
if (!response.ok) return null
|
||||
const data = (await response.json()) as { version: string }
|
||||
return data.version || null
|
||||
@@ -102,7 +121,7 @@ export function autoUpdateOnStartup(currentVersion: string): void {
|
||||
return
|
||||
}
|
||||
|
||||
const { updateCommand } = getInstallationInfo()
|
||||
const { updateCommand } = getInstallationInfo(currentVersion)
|
||||
if (!updateCommand) {
|
||||
return
|
||||
}
|
||||
@@ -113,7 +132,7 @@ export function autoUpdateOnStartup(currentVersion: string): void {
|
||||
|
||||
async function checkAndUpdate(currentVersion: string, updateCommand: string): Promise<void> {
|
||||
try {
|
||||
const latestVersion = await getLatestVersion()
|
||||
const latestVersion = await getLatestVersion(currentVersion)
|
||||
if (!latestVersion) return
|
||||
|
||||
// Only update if latest is newer
|
||||
@@ -138,10 +157,10 @@ async function checkAndUpdate(currentVersion: string, updateCommand: string): Pr
|
||||
export async function checkForUpdates(currentVersion: string, options?: { verbose?: boolean }) {
|
||||
printInfo("Checking for updates...")
|
||||
|
||||
const { updateCommand, packageManager } = getInstallationInfo()
|
||||
const { updateCommand, packageManager } = getInstallationInfo(currentVersion)
|
||||
|
||||
try {
|
||||
const latestVersion = await getLatestVersion()
|
||||
const latestVersion = await getLatestVersion(currentVersion)
|
||||
if (!latestVersion) {
|
||||
printWarning("Failed to check for updates: could not fetch latest version")
|
||||
exit(1)
|
||||
@@ -218,21 +237,61 @@ export async function checkForUpdates(currentVersion: string, options?: { verbos
|
||||
}
|
||||
}
|
||||
|
||||
interface ParsedVersion {
|
||||
base: number[]
|
||||
isNightly: boolean
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two semantic version strings
|
||||
* Parse a version string into its components.
|
||||
* Handles both stable versions (2.0.0) and nightly versions (2.0.0-nightly.1736365200).
|
||||
*/
|
||||
function parseVersion(version: string): ParsedVersion {
|
||||
const nightlyMatch = version.match(/^(\d+\.\d+\.\d+)-nightly\.(\d+)$/)
|
||||
if (nightlyMatch) {
|
||||
return {
|
||||
base: nightlyMatch[1].split(".").map(Number),
|
||||
isNightly: true,
|
||||
timestamp: parseInt(nightlyMatch[2], 10),
|
||||
}
|
||||
}
|
||||
return {
|
||||
base: version.split(".").map(Number),
|
||||
isNightly: false,
|
||||
timestamp: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two semantic version strings.
|
||||
* Handles both stable versions and nightly versions.
|
||||
* Nightly versions are compared by their timestamps.
|
||||
* Returns: 1 if v1 > v2, -1 if v1 < v2, 0 if equal
|
||||
*/
|
||||
function compareVersions(v1: string, v2: string): number {
|
||||
const parts1 = v1.split(".").map(Number)
|
||||
const parts2 = v2.split(".").map(Number)
|
||||
const p1 = parseVersion(v1)
|
||||
const p2 = parseVersion(v2)
|
||||
|
||||
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
||||
const part1 = parts1[i] || 0
|
||||
const part2 = parts2[i] || 0
|
||||
// Compare base versions first
|
||||
for (let i = 0; i < Math.max(p1.base.length, p2.base.length); i++) {
|
||||
const part1 = p1.base[i] || 0
|
||||
const part2 = p2.base[i] || 0
|
||||
|
||||
if (part1 > part2) return 1
|
||||
if (part1 < part2) return -1
|
||||
}
|
||||
|
||||
// Base versions are equal, check nightly status
|
||||
// Nightly is considered less than stable (it's a pre-release)
|
||||
if (p1.isNightly && !p2.isNightly) return -1
|
||||
if (!p1.isNightly && p2.isNightly) return 1
|
||||
|
||||
// Both are nightly, compare timestamps
|
||||
if (p1.isNightly && p2.isNightly) {
|
||||
if (p1.timestamp > p2.timestamp) return 1
|
||||
if (p1.timestamp < p2.timestamp) return -1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user