When CLI is upgraded (e.g., 0.24.0 → 0.24.1), the database version is now automatically updated to match the CLI version during PersistentPreRun. This fixes the recurring UX issue where bd doctor shows version mismatch after every CLI upgrade. Implementation: - Added autoMigrateOnVersionBump() function in version_tracking.go - Calls after trackBdVersion() in PersistentPreRun - Best-effort and silent failures to avoid disrupting commands - Only updates bd_version metadata field - Includes comprehensive test coverage Changes: - cmd/bd/main.go: Call autoMigrateOnVersionBump() in PersistentPreRun - cmd/bd/version_tracking.go: Implement auto-migration logic - cmd/bd/version_tracking_test.go: Add tests for auto-migration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
198 lines
5.9 KiB
Go
198 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/steveyegge/beads/internal/beads"
|
|
"github.com/steveyegge/beads/internal/configfile"
|
|
"github.com/steveyegge/beads/internal/debug"
|
|
"github.com/steveyegge/beads/internal/storage/sqlite"
|
|
)
|
|
|
|
// trackBdVersion checks if bd version has changed since last run and updates metadata.json.
|
|
// This function is best-effort - failures are silent to avoid disrupting commands.
|
|
// Sets global variables versionUpgradeDetected and previousVersion if upgrade detected.
|
|
//
|
|
// bd-loka: Built-in version tracking for upgrade awareness
|
|
func trackBdVersion() {
|
|
// Find the beads directory
|
|
beadsDir := beads.FindBeadsDir()
|
|
if beadsDir == "" {
|
|
// No .beads directory found - this is fine (e.g., bd init, bd version, etc.)
|
|
return
|
|
}
|
|
|
|
// Load current config
|
|
cfg, err := configfile.Load(beadsDir)
|
|
if err != nil {
|
|
// Silent failure - config might not exist yet
|
|
return
|
|
}
|
|
if cfg == nil {
|
|
// No config file yet - create one with current version
|
|
cfg = configfile.DefaultConfig()
|
|
cfg.LastBdVersion = Version
|
|
_ = cfg.Save(beadsDir) // Best effort
|
|
return
|
|
}
|
|
|
|
// Check if version changed
|
|
if cfg.LastBdVersion != "" && cfg.LastBdVersion != Version {
|
|
// Version upgrade detected!
|
|
versionUpgradeDetected = true
|
|
previousVersion = cfg.LastBdVersion
|
|
}
|
|
|
|
// Update metadata.json with current version (best effort)
|
|
// Only write if version actually changed to minimize I/O
|
|
// Also update on first run (when LastBdVersion is empty) to initialize tracking
|
|
if cfg.LastBdVersion != Version {
|
|
cfg.LastBdVersion = Version
|
|
_ = cfg.Save(beadsDir) // Silent failure is fine
|
|
}
|
|
}
|
|
|
|
// getVersionsSince returns all version changes since the given version.
|
|
// If sinceVersion is empty, returns all known versions.
|
|
// Returns changes in chronological order (oldest first).
|
|
//
|
|
// Note: versionChanges array is in reverse chronological order (newest first),
|
|
// so we return elements before the found index and reverse the slice.
|
|
func getVersionsSince(sinceVersion string) []VersionChange {
|
|
if sinceVersion == "" {
|
|
// Return all versions (already in reverse chronological, but kept for compatibility)
|
|
return versionChanges
|
|
}
|
|
|
|
// Find the index of sinceVersion
|
|
// versionChanges is ordered newest-first: [0.23.0, 0.22.1, 0.22.0, 0.21.0]
|
|
startIdx := -1
|
|
for i, vc := range versionChanges {
|
|
if vc.Version == sinceVersion {
|
|
startIdx = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if startIdx == -1 {
|
|
// sinceVersion not found in our changelog - return all versions
|
|
// (user might be upgrading from a very old version)
|
|
return versionChanges
|
|
}
|
|
|
|
if startIdx == 0 {
|
|
// Already on the newest version
|
|
return []VersionChange{}
|
|
}
|
|
|
|
// Return versions before sinceVersion (those are newer)
|
|
// Then reverse to get chronological order (oldest first)
|
|
newerVersions := versionChanges[:startIdx]
|
|
|
|
// Reverse the slice to get chronological order
|
|
result := make([]VersionChange, len(newerVersions))
|
|
for i := range newerVersions {
|
|
result[i] = newerVersions[len(newerVersions)-1-i]
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// maybeShowUpgradeNotification displays a one-time upgrade notification if version changed.
|
|
// This is called by commands like 'bd ready' and 'bd list' to inform users of upgrades.
|
|
func maybeShowUpgradeNotification() {
|
|
// Only show if upgrade detected and not yet acknowledged
|
|
if !versionUpgradeDetected || upgradeAcknowledged {
|
|
return
|
|
}
|
|
|
|
// Mark as acknowledged so we only show once per session
|
|
upgradeAcknowledged = true
|
|
|
|
// Display notification
|
|
fmt.Printf("🔄 bd upgraded from v%s to v%s since last use\n", previousVersion, Version)
|
|
fmt.Println("💡 Run 'bd upgrade review' to see what changed")
|
|
fmt.Println()
|
|
}
|
|
|
|
// autoMigrateOnVersionBump automatically migrates the database when CLI version changes.
|
|
// This function is best-effort - failures are silent to avoid disrupting commands.
|
|
// Called from PersistentPreRun after trackBdVersion().
|
|
//
|
|
// bd-jgxi: Auto-migrate database on CLI version bump
|
|
func autoMigrateOnVersionBump() {
|
|
// Only migrate if version upgrade was detected
|
|
if !versionUpgradeDetected {
|
|
return
|
|
}
|
|
|
|
// Find the beads directory
|
|
beadsDir := beads.FindBeadsDir()
|
|
if beadsDir == "" {
|
|
// No .beads directory - nothing to migrate
|
|
return
|
|
}
|
|
|
|
// Load config to get database path
|
|
cfg, err := configfile.Load(beadsDir)
|
|
if err != nil || cfg == nil {
|
|
// Config load failed or doesn't exist - skip migration
|
|
debug.Logf("auto-migrate: skipping migration, config load failed: %v", err)
|
|
return
|
|
}
|
|
|
|
// Get database path
|
|
dbPath := cfg.DatabasePath(beadsDir)
|
|
|
|
// Check if database exists
|
|
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
|
|
// No database file - nothing to migrate
|
|
debug.Logf("auto-migrate: skipping migration, database does not exist: %s", dbPath)
|
|
return
|
|
}
|
|
|
|
// Open database to check current version
|
|
ctx := context.Background()
|
|
store, err := sqlite.New(ctx, dbPath)
|
|
if err != nil {
|
|
// Failed to open database - skip migration
|
|
debug.Logf("auto-migrate: failed to open database: %v", err)
|
|
return
|
|
}
|
|
|
|
// Get current database version
|
|
dbVersion, err := store.GetMetadata(ctx, "bd_version")
|
|
if err != nil {
|
|
// Failed to read version - skip migration
|
|
debug.Logf("auto-migrate: failed to read database version: %v", err)
|
|
_ = store.Close()
|
|
return
|
|
}
|
|
|
|
// Check if migration is needed
|
|
if dbVersion == Version {
|
|
// Database is already at current version
|
|
debug.Logf("auto-migrate: database already at version %s", Version)
|
|
_ = store.Close()
|
|
return
|
|
}
|
|
|
|
// Perform migration: update database version
|
|
debug.Logf("auto-migrate: migrating database from %s to %s", dbVersion, Version)
|
|
if err := store.SetMetadata(ctx, "bd_version", Version); err != nil {
|
|
// Migration failed - log and continue
|
|
debug.Logf("auto-migrate: failed to update database version: %v", err)
|
|
_ = store.Close()
|
|
return
|
|
}
|
|
|
|
// Close database
|
|
if err := store.Close(); err != nil {
|
|
debug.Logf("auto-migrate: warning: failed to close database: %v", err)
|
|
}
|
|
|
|
debug.Logf("auto-migrate: successfully migrated database to version %s", Version)
|
|
}
|