From 388fc164eb868a455146f6ad8977d4674dfb9308 Mon Sep 17 00:00:00 2001 From: Florin Nebenfuehr Date: Mon, 24 Nov 2025 15:21:05 +0100 Subject: [PATCH 1/2] fix: improve version command UX and consolidate update code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use Commander's native .version() to prevent help text display - Suggest `worktree update` instead of `npm update -g` - Move PackageJson interface to shared lib/types.ts - Rename utils/update-checker.ts to utils/update.ts - Export writeCache and make getVersionInfo update cache 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .changeset/fix-version-ux.md | 5 ++++ src/commands/update.ts | 13 +++++----- src/index.ts | 28 ++++++++++++---------- src/lib/types.ts | 8 +++++++ src/utils/{update-checker.ts => update.ts} | 18 ++++++-------- 5 files changed, 42 insertions(+), 30 deletions(-) create mode 100644 .changeset/fix-version-ux.md rename src/utils/{update-checker.ts => update.ts} (89%) diff --git a/.changeset/fix-version-ux.md b/.changeset/fix-version-ux.md new file mode 100644 index 0000000..57bd952 --- /dev/null +++ b/.changeset/fix-version-ux.md @@ -0,0 +1,5 @@ +--- +"@fnebenfuehr/worktree-cli": patch +--- + +Fix version command UX: remove help text display, suggest `worktree update` instead of `npm update -g`, consolidate update checking code, and make fresh version checks update the cache diff --git a/src/commands/update.ts b/src/commands/update.ts index c2e2d4b..e26e809 100644 --- a/src/commands/update.ts +++ b/src/commands/update.ts @@ -1,12 +1,8 @@ import { spawn } from 'node:child_process'; +import type { PackageJson } from '@/lib/types'; import { WorktreeError } from '@/utils/errors'; import { intro, log, outro, spinner } from '@/utils/prompts'; -import { fetchLatestVersion, isNewerVersion } from '@/utils/update-checker'; - -interface PackageJson { - name: string; - version: string; -} +import { fetchLatestVersion, isNewerVersion, writeCache } from '@/utils/update'; /** * Runs npm update -g for the package and returns the new version @@ -145,6 +141,11 @@ export async function getVersionInfo( const latest = await fetchLatestVersion(pkg.name); const updateAvailable = latest ? isNewerVersion(pkg.version, latest) : false; + await writeCache({ + lastCheck: Date.now(), + latestVersion: latest || undefined, + }); + return { current: pkg.version, latest, diff --git a/src/index.ts b/src/index.ts index 5a5e780..ef610a6 100755 --- a/src/index.ts +++ b/src/index.ts @@ -18,7 +18,7 @@ import { switchCommand } from '@/commands/switch'; import { getVersionInfo, updateCommand } from '@/commands/update'; import { UserCancelledError, WorktreeError } from '@/utils/errors'; import { log } from '@/utils/prompts'; -import { checkForUpdates } from '@/utils/update-checker'; +import { checkForUpdates } from '@/utils/update'; import packageJson from '../package.json'; const VERSION = packageJson.version; @@ -55,20 +55,22 @@ const program = new Command(); program .name('worktree') .description('A modern CLI tool for managing git worktrees with ease') - .option('-v, --version', 'Show version') + .version(VERSION, '-v, --version', 'Show version') .option('--verbose', 'Enable verbose output') - .on('option:version', async () => { - console.log(VERSION); - try { - const info = await getVersionInfo(packageJson); - if (info.updateAvailable && info.latest) { - console.log(`\nUpdate available: ${info.current} → ${info.latest}`); - console.log(`Run: npm update -g ${packageJson.name}`); + .configureOutput({ + outputVersion: async (str) => { + console.log(str.trim()); + try { + const info = await getVersionInfo(packageJson); + if (info.updateAvailable && info.latest) { + console.log(`\nUpdate available: ${info.current} → ${info.latest}`); + console.log(`Run: worktree update`); + } + } catch { + // Silently ignore update check errors } - } catch { - // Silently ignore update check errors - } - process.exit(0); + process.exit(0); + }, }) .addHelpText( 'after', diff --git a/src/lib/types.ts b/src/lib/types.ts index 5a3cace..65d1b8f 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -2,6 +2,14 @@ * Shared types for the worktree CLI library */ +/** + * Package.json structure for update checking + */ +export interface PackageJson { + name: string; + version: string; +} + /** * Information about a git worktree */ diff --git a/src/utils/update-checker.ts b/src/utils/update.ts similarity index 89% rename from src/utils/update-checker.ts rename to src/utils/update.ts index 5c61a9a..f2c9b6f 100644 --- a/src/utils/update-checker.ts +++ b/src/utils/update.ts @@ -2,6 +2,7 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises'; import { homedir, tmpdir } from 'node:os'; import { join } from 'node:path'; import { z } from 'zod'; +import type { PackageJson } from '@/lib/types'; import { log } from '@/utils/prompts'; import { tryCatch } from '@/utils/try-catch'; @@ -10,12 +11,7 @@ const UpdateCheckCacheSchema = z.object({ latestVersion: z.string().optional(), }); -type UpdateCheckCache = z.infer; - -interface PackageJson { - name: string; - version: string; -} +export type UpdateCheckCache = z.infer; const CACHE_FILENAME = 'update-check.json'; const PRERELEASE_PATTERN = /-(?:alpha|beta|rc|pre|canary|next|dev)/; @@ -57,7 +53,7 @@ async function readCache(): Promise { return data; } -async function writeCache(cache: UpdateCheckCache): Promise { +export async function writeCache(cache: UpdateCheckCache): Promise { const { error } = await tryCatch(async () => { await ensureCacheDir(); await writeFile(cacheFile, JSON.stringify(cache, null, 2), 'utf-8'); @@ -140,7 +136,7 @@ export async function checkForUpdates(pkg: PackageJson, checkIntervalMs: number) if (cache && now - cache.lastCheck < checkIntervalMs) { if (cache.latestVersion && isNewerVersion(pkg.version, cache.latestVersion)) { - displayUpdateMessage(pkg.version, cache.latestVersion, pkg.name); + displayUpdateMessage(pkg.version, cache.latestVersion); } return; } @@ -153,10 +149,10 @@ export async function checkForUpdates(pkg: PackageJson, checkIntervalMs: number) }); if (latestVersion && isNewerVersion(pkg.version, latestVersion)) { - displayUpdateMessage(pkg.version, latestVersion, pkg.name); + displayUpdateMessage(pkg.version, latestVersion); } } -function displayUpdateMessage(current: string, latest: string, packageName: string): void { - log.info(`Update available: ${current} → ${latest}\nRun: npm update -g ${packageName}`); +function displayUpdateMessage(current: string, latest: string): void { + log.info(`Update available: ${current} → ${latest}\nRun: worktree update`); } From 0f057eff05890ec12d141d7d29fc9d9460951aef Mon Sep 17 00:00:00 2001 From: Florin Nebenfuehr Date: Mon, 24 Nov 2025 22:45:36 +0100 Subject: [PATCH 2/2] fix: update test imports and fix version command update check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update test imports from @/utils/update-checker to @/utils/update after module rename in commit 388fc16 - Fix version command to properly show update notifications - Replace invalid outputVersion config with pre-parse argv check - Commander's OutputConfiguration doesn't support outputVersion Tests: 308 pass, 0 fail 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/index.ts | 32 ++++++++++++++++---------------- tests/update-checker.test.ts | 2 +- tests/update-command.test.ts | 8 ++++---- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/index.ts b/src/index.ts index ef610a6..9cf9293 100755 --- a/src/index.ts +++ b/src/index.ts @@ -50,28 +50,28 @@ function handleCommandError(fn: () => Promise) { }; } +// Handle version flag before Commander parses +if (process.argv.includes('--version') || process.argv.includes('-v')) { + console.log(VERSION); + try { + const info = await getVersionInfo(packageJson); + if (info.updateAvailable && info.latest) { + console.log(`\nUpdate available: ${info.current} → ${info.latest}`); + console.log(`Run: worktree update`); + } + } catch { + // Silently ignore update check errors + } + process.exit(0); +} + const program = new Command(); program .name('worktree') .description('A modern CLI tool for managing git worktrees with ease') - .version(VERSION, '-v, --version', 'Show version') + .option('-v, --version', 'Show version') .option('--verbose', 'Enable verbose output') - .configureOutput({ - outputVersion: async (str) => { - console.log(str.trim()); - try { - const info = await getVersionInfo(packageJson); - if (info.updateAvailable && info.latest) { - console.log(`\nUpdate available: ${info.current} → ${info.latest}`); - console.log(`Run: worktree update`); - } - } catch { - // Silently ignore update check errors - } - process.exit(0); - }, - }) .addHelpText( 'after', ` diff --git a/tests/update-checker.test.ts b/tests/update-checker.test.ts index b0f14f6..83e0ef3 100644 --- a/tests/update-checker.test.ts +++ b/tests/update-checker.test.ts @@ -3,7 +3,7 @@ import { rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import * as prompts from '@/utils/prompts'; -import { checkForUpdates, setCacheDir } from '@/utils/update-checker'; +import { checkForUpdates, setCacheDir } from '@/utils/update'; describe('update-checker', () => { let originalFetch: typeof globalThis.fetch; diff --git a/tests/update-command.test.ts b/tests/update-command.test.ts index 7dbf078..01be499 100644 --- a/tests/update-command.test.ts +++ b/tests/update-command.test.ts @@ -6,7 +6,7 @@ import { spawn } from 'bun'; import * as updateModule from '@/commands/update'; import { getVersionInfo, updateCommand } from '@/commands/update'; import * as prompts from '@/utils/prompts'; -import { setCacheDir } from '@/utils/update-checker'; +import { setCacheDir } from '@/utils/update'; const CLI_PATH = join(import.meta.dir, '../src/index.ts'); @@ -164,17 +164,17 @@ describe('update command', () => { describe('update-checker exports', () => { test('fetchLatestVersion is exported', async () => { - const { fetchLatestVersion } = await import('@/utils/update-checker'); + const { fetchLatestVersion } = await import('@/utils/update'); expect(typeof fetchLatestVersion).toBe('function'); }); test('isNewerVersion is exported', async () => { - const { isNewerVersion } = await import('@/utils/update-checker'); + const { isNewerVersion } = await import('@/utils/update'); expect(typeof isNewerVersion).toBe('function'); }); test('isNewerVersion correctly compares versions', async () => { - const { isNewerVersion } = await import('@/utils/update-checker'); + const { isNewerVersion } = await import('@/utils/update'); expect(isNewerVersion('1.0.0', '2.0.0')).toBe(true); expect(isNewerVersion('1.0.0', '1.1.0')).toBe(true);