From 3badd3eaa3999b0176d0de30d79ae212997f083e Mon Sep 17 00:00:00 2001 From: Christopher Tso Date: Tue, 5 May 2026 02:33:52 +0200 Subject: [PATCH 1/2] fix(cli): use next dist-tag for self-update when on a prerelease version When the installed version has a prerelease identifier (e.g. 4.25.3-next.1), the update check and self-update command now query the `next` npm dist-tag instead of `latest`. This prevents suggesting or applying a downgrade to the older stable release. Co-Authored-By: Claude Sonnet 4.6 --- apps/cli/src/commands/self/index.ts | 6 ++++-- apps/cli/src/self-update.ts | 19 +++++++++++++------ apps/cli/src/update-check.ts | 10 ++++++---- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/apps/cli/src/commands/self/index.ts b/apps/cli/src/commands/self/index.ts index 8ab7ad2a..41a86481 100644 --- a/apps/cli/src/commands/self/index.ts +++ b/apps/cli/src/commands/self/index.ts @@ -4,6 +4,7 @@ import { detectInstallScope, detectPackageManager, fetchLatestVersion, + getDistTagForVersion, performSelfUpdate, } from '../../self-update.js'; @@ -33,10 +34,11 @@ const updateCommand = command({ } const currentVersion = packageJson.version; + const distTag = getDistTagForVersion(currentVersion); console.log(`Current version: ${currentVersion}`); console.log('Checking for updates...'); - const latestVersion = await fetchLatestVersion(); + const latestVersion = await fetchLatestVersion(distTag); if (latestVersion && latestVersion === currentVersion) { console.log(`Already up to date (${currentVersion}).`); return; @@ -49,7 +51,7 @@ const updateCommand = command({ const scopeLabel = scope === 'local' ? 'local project install' : 'global install'; console.log(`Updating agentv using ${pm} (${scopeLabel})...\n`); - const result = await performSelfUpdate({ pm, currentVersion, scope }); + const result = await performSelfUpdate({ pm, currentVersion, versionRange: distTag, scope }); if (!result.success) { console.error('\nUpdate failed.'); diff --git a/apps/cli/src/self-update.ts b/apps/cli/src/self-update.ts index 34982b3f..f984931f 100644 --- a/apps/cli/src/self-update.ts +++ b/apps/cli/src/self-update.ts @@ -9,7 +9,8 @@ * version specifier (e.g., `agentv@">=4.1.0"`). This ensures the update * respects the project's constraints and avoids unintended major-version jumps. * - * When called from `agentv self update` (no range), it installs `@latest`. + * When called from `agentv self update` (no range), it installs `@latest` for stable + * versions or `@next` when the current version has a prerelease identifier. * * Install scope detection: if `process.argv[1]` contains `node_modules`, * agentv was invoked from a local project dependency (e.g. `npx agentv` or @@ -30,7 +31,12 @@ import { existsSync } from 'node:fs'; import { get } from 'node:https'; import { basename, dirname, join, win32 } from 'node:path'; -const NPM_REGISTRY_URL = 'https://registry.npmjs.org/agentv/latest'; +const NPM_REGISTRY_BASE = 'https://registry.npmjs.org/agentv/'; + +/** Returns 'next' if the version has a prerelease identifier, 'latest' otherwise. */ +export function getDistTagForVersion(version: string): 'next' | 'latest' { + return version.includes('-') ? 'next' : 'latest'; +} /** * Detect package manager from the script path. @@ -111,11 +117,11 @@ function runCommand(cmd: string, args: string[]): Promise<{ exitCode: number; st /** * Fetch the latest published version of agentv from the npm registry. - * Returns null on network errors or timeouts (best-effort). + * Pass distTag='next' for prerelease channels. Returns null on network errors. */ -export function fetchLatestVersion(): Promise { +export function fetchLatestVersion(distTag: 'latest' | 'next' = 'latest'): Promise { return new Promise((resolve) => { - const req = get(NPM_REGISTRY_URL, { timeout: 5000 }, (res) => { + const req = get(`${NPM_REGISTRY_BASE}${distTag}`, { timeout: 5000 }, (res) => { if (res.statusCode !== 200) { res.resume(); resolve(null); @@ -226,7 +232,8 @@ export async function performSelfUpdate(options?: { }> { const pm = options?.pm ?? detectPackageManager(); const currentVersion = options?.currentVersion ?? 'unknown'; - const versionSpec = options?.versionRange ?? 'latest'; + const versionSpec = + options?.versionRange ?? getDistTagForVersion(currentVersion === 'unknown' ? '' : currentVersion); const scope = options?.scope ?? detectInstallScope(); const args = getInstallArgs(pm, versionSpec, scope); diff --git a/apps/cli/src/update-check.ts b/apps/cli/src/update-check.ts index d50ad775..c39426d1 100644 --- a/apps/cli/src/update-check.ts +++ b/apps/cli/src/update-check.ts @@ -6,7 +6,7 @@ import { getAgentvConfigDir } from '@agentv/core'; const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours const CONFIG_DIR = getAgentvConfigDir(); const CACHE_FILE = 'version-check.json'; -const NPM_REGISTRY_URL = 'https://registry.npmjs.org/agentv/latest'; +const NPM_REGISTRY_BASE = 'https://registry.npmjs.org/agentv/'; export interface UpdateCache { latestVersion: string; @@ -66,16 +66,17 @@ export function buildNotice(currentVersion: string, latestVersion: string | null * fetches latest version from npm and updates the cache file. The child * survives even if the parent calls process.exit(). */ -export function backgroundUpdateCheck(): void { +export function backgroundUpdateCheck(distTag: 'latest' | 'next' = 'latest'): void { const dir = CONFIG_DIR; const filePath = join(dir, CACHE_FILE); + const registryUrl = `${NPM_REGISTRY_BASE}${distTag}`; const script = ` const https = require('https'); const fs = require('fs'); const dir = ${JSON.stringify(dir)}; const filePath = ${JSON.stringify(filePath)}; - https.get(${JSON.stringify(NPM_REGISTRY_URL)}, { timeout: 5000 }, (res) => { + https.get(${JSON.stringify(registryUrl)}, { timeout: 5000 }, (res) => { if (res.statusCode !== 200) { res.resume(); process.exit(); } let body = ''; res.on('data', (c) => body += c); @@ -110,9 +111,10 @@ export async function getUpdateNotice(currentVersion: string): Promise Date: Tue, 5 May 2026 02:35:17 +0200 Subject: [PATCH 2/2] style: fix biome line-length formatting in self-update.ts Co-Authored-By: Claude Sonnet 4.6 --- apps/cli/src/self-update.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/cli/src/self-update.ts b/apps/cli/src/self-update.ts index f984931f..7f5a833b 100644 --- a/apps/cli/src/self-update.ts +++ b/apps/cli/src/self-update.ts @@ -233,7 +233,8 @@ export async function performSelfUpdate(options?: { const pm = options?.pm ?? detectPackageManager(); const currentVersion = options?.currentVersion ?? 'unknown'; const versionSpec = - options?.versionRange ?? getDistTagForVersion(currentVersion === 'unknown' ? '' : currentVersion); + options?.versionRange ?? + getDistTagForVersion(currentVersion === 'unknown' ? '' : currentVersion); const scope = options?.scope ?? detectInstallScope(); const args = getInstallArgs(pm, versionSpec, scope);