diff --git a/pkg/cli/update_extension_check.go b/pkg/cli/update_extension_check.go index 9e779287dcc..3d613f8083c 100644 --- a/pkg/cli/update_extension_check.go +++ b/pkg/cli/update_extension_check.go @@ -5,10 +5,14 @@ import ( "os" "strings" + "github.com/cli/go-gh/v2/pkg/api" "github.com/github/gh-aw/pkg/console" + "github.com/github/gh-aw/pkg/logger" "github.com/github/gh-aw/pkg/workflow" ) +var updateExtensionCheckLog = logger.New("cli:update_extension_check") + // checkExtensionUpdate checks if a newer version of gh-aw is available func checkExtensionUpdate(verbose bool) error { if verbose { @@ -61,56 +65,94 @@ func isAuthenticationError(output string) bool { strings.Contains(lowerOutput, "invalid token") } -// ensureLatestExtensionVersion checks if a newer version of gh-aw is available -// and returns an error if an update is needed. This is used by the upgrade command -// to ensure users are on the latest version before upgrading workflows. +// ensureLatestExtensionVersion checks if the current release matches the latest release +// and issues a warning if an update is needed. This function fails silently if the +// release URL is not available or blocked. func ensureLatestExtensionVersion(verbose bool) error { if verbose { fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Checking for gh-aw extension updates...")) } - // Run gh extension upgrade --dry-run to check for updates - output, err := workflow.RunGHCombined("Checking for extension updates...", "extension", "upgrade", "github/gh-aw", "--dry-run") - outputStr := strings.TrimSpace(string(output)) + // Get current version + currentVersion := GetVersion() + updateExtensionCheckLog.Printf("Current version: %s", currentVersion) - // Check for authentication errors (missing or invalid token) - if err != nil || isAuthenticationError(outputStr) { + // Skip check for non-release versions (dev builds) + if !workflow.IsReleasedVersion(currentVersion) { + updateExtensionCheckLog.Print("Not a released version, skipping update check") if verbose { - if err != nil { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to check for extension updates: %v", err))) - } else { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Authentication required to check for updates")) - } + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Skipping version check (development build)")) } - // If we can't check for updates due to auth issues, allow the upgrade to proceed return nil } - if verbose { - fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Extension update check output: %s", outputStr))) + // Query GitHub API for latest release + latestVersion, err := getLatestReleaseVersion(verbose) + if err != nil { + // Fail silently - don't block upgrade if we can't check for updates + updateExtensionCheckLog.Printf("Failed to check for updates (silently ignoring): %v", err) + if verbose { + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Could not check for updates: %v", err))) + } + return nil } - // Parse the output to see if an update is available - // Expected format: "[agentics]: would have upgraded from v0.14.0 to v0.18.1" - lines := strings.Split(outputStr, "\n") - for _, line := range lines { - if strings.Contains(line, "[agentics]: would have upgraded from") { - fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, console.FormatErrorMessage("gh-aw extension is not on the latest version")) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(line)) - fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Please upgrade the gh extension first:")) - fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" gh extension upgrade github/gh-aw")) - fmt.Fprintln(os.Stderr, "") - return fmt.Errorf("gh-aw extension must be upgraded before running this command") - } + if latestVersion == "" { + updateExtensionCheckLog.Print("Could not determine latest version") + return nil } - if strings.Contains(outputStr, "✓ Successfully checked extension upgrades") { + updateExtensionCheckLog.Printf("Latest version: %s", latestVersion) + + // Normalize versions for comparison (remove 'v' prefix) + currentVersionNormalized := strings.TrimPrefix(currentVersion, "v") + latestVersionNormalized := strings.TrimPrefix(latestVersion, "v") + + // Compare versions + if currentVersionNormalized == latestVersionNormalized { if verbose { fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("✓ gh-aw extension is up to date")) } + updateExtensionCheckLog.Print("Extension is up to date") + return nil } + // Check if we're on a newer version (development/prerelease) + if currentVersionNormalized > latestVersionNormalized { + updateExtensionCheckLog.Printf("Current version (%s) appears newer than latest release (%s)", currentVersion, latestVersion) + if verbose { + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Running a development or pre-release version")) + } + return nil + } + + // A newer version is available - display warning message (not error) + updateExtensionCheckLog.Printf("Newer version available: %s (current: %s)", latestVersion, currentVersion) + fmt.Fprintln(os.Stderr, "") + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("A newer version of gh-aw is available: %s (current: %s)", latestVersion, currentVersion))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Consider upgrading with: gh extension upgrade github/gh-aw")) + fmt.Fprintln(os.Stderr, "") + return nil } + +// getLatestReleaseVersion queries GitHub API for the latest release version of gh-aw +func getLatestReleaseVersion(verbose bool) (string, error) { + updateExtensionCheckLog.Print("Querying GitHub API for latest release...") + + // Create GitHub REST client using go-gh + client, err := api.NewRESTClient(api.ClientOptions{}) + if err != nil { + return "", fmt.Errorf("failed to create GitHub client: %w", err) + } + + // Query the latest release + var release Release + err = client.Get("repos/github/gh-aw/releases/latest", &release) + if err != nil { + return "", fmt.Errorf("failed to query latest release: %w", err) + } + + updateExtensionCheckLog.Printf("Latest release: %s", release.TagName) + return release.TagName, nil +} diff --git a/pkg/cli/update_extension_check_test.go b/pkg/cli/update_extension_check_test.go index c709cad6e01..6455ce024ce 100644 --- a/pkg/cli/update_extension_check_test.go +++ b/pkg/cli/update_extension_check_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestIsAuthenticationError(t *testing.T) { @@ -58,3 +59,32 @@ func TestIsAuthenticationError(t *testing.T) { }) } } + +func TestEnsureLatestExtensionVersion_DevBuild(t *testing.T) { + // Save original version and restore after test + originalVersion := GetVersion() + defer SetVersionInfo(originalVersion) + + // Set a dev version + SetVersionInfo("dev") + + // Should return nil without error for dev builds + err := ensureLatestExtensionVersion(false) + require.NoError(t, err, "Should not return error for dev builds") +} + +func TestEnsureLatestExtensionVersion_SilentFailure(t *testing.T) { + // This test verifies that network/API errors are handled silently + // The actual API call will fail in the test environment but should not return an error + + // Save original version and restore after test + originalVersion := GetVersion() + defer SetVersionInfo(originalVersion) + + // Set a valid release version + SetVersionInfo("v0.1.0") + + // Should return nil even if API call fails (fails silently) + err := ensureLatestExtensionVersion(false) + require.NoError(t, err, "Should fail silently on API errors") +}