From f12ae1ee7830c8b106fb4f1b822d83f91c19080b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 10 Nov 2025 16:46:52 -0800 Subject: [PATCH] Add RenameLocalApp function to waveappstore (#2536) ## Plan: Add RenameLocalApp function to waveappstore - [x] Analyze existing code patterns in waveappstore.go - [x] Implement RenameLocalApp(appName string, newAppName string) error function - Validate newAppName using existing validation - Check if local/[appname] exists and rename to local/[newAppName] if it does - Check if draft/[appname] exists and rename to draft/[newAppName] if it does - Handle errors appropriately including rollback on partial failure - [x] Add documentation comments - [x] Run security checks (CodeQL - no issues found) ## Summary Successfully implemented `RenameLocalApp(appName string, newAppName string) error` function that: - Renames local apps in both `local/` and `draft/` namespaces - Validates app names and checks for conflicts - Implements proper error handling with rollback on partial failure - Passes all security checks with zero vulnerabilities Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sawka <2722291+sawka@users.noreply.github.com> --- pkg/waveappstore/waveappstore.go | 82 +++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/pkg/waveappstore/waveappstore.go b/pkg/waveappstore/waveappstore.go index 1da4c5927..a4ab7f117 100644 --- a/pkg/waveappstore/waveappstore.go +++ b/pkg/waveappstore/waveappstore.go @@ -498,7 +498,6 @@ func ListAllEditableApps() ([]string, error) { return appIds, nil } - func DraftHasLocalVersion(draftAppId string) (bool, error) { if err := ValidateAppId(draftAppId); err != nil { return false, fmt.Errorf("invalid appId: %w", err) @@ -521,3 +520,84 @@ func DraftHasLocalVersion(draftAppId string) (bool, error) { return true, nil } + +// RenameLocalApp renames a local app by renaming its directories in both the local and draft namespaces. +// It takes the current app name and the new app name (without namespace prefixes). +// Both local/[appName] and draft/[appName] will be renamed if they exist. +// Returns an error if the app doesn't exist in either namespace, if the new name is invalid, +// or if the new name conflicts with an existing app. +func RenameLocalApp(appName string, newAppName string) error { + // Validate the old app name by constructing a valid appId + oldLocalAppId := MakeAppId(AppNSLocal, appName) + if err := ValidateAppId(oldLocalAppId); err != nil { + return fmt.Errorf("invalid app name: %w", err) + } + + // Validate the new app name by constructing a valid appId + newLocalAppId := MakeAppId(AppNSLocal, newAppName) + if err := ValidateAppId(newLocalAppId); err != nil { + return fmt.Errorf("invalid new app name: %w", err) + } + + homeDir := wavebase.GetHomeDir() + waveappsDir := filepath.Join(homeDir, "waveapps") + + oldLocalDir := filepath.Join(waveappsDir, AppNSLocal, appName) + newLocalDir := filepath.Join(waveappsDir, AppNSLocal, newAppName) + oldDraftDir := filepath.Join(waveappsDir, AppNSDraft, appName) + newDraftDir := filepath.Join(waveappsDir, AppNSDraft, newAppName) + + // Check if at least one of the apps exists + localExists := false + draftExists := false + if _, err := os.Stat(oldLocalDir); err == nil { + localExists = true + } else if !os.IsNotExist(err) { + return fmt.Errorf("failed to check local app: %w", err) + } + + if _, err := os.Stat(oldDraftDir); err == nil { + draftExists = true + } else if !os.IsNotExist(err) { + return fmt.Errorf("failed to check draft app: %w", err) + } + + if !localExists && !draftExists { + return fmt.Errorf("app '%s' does not exist in local or draft namespace", appName) + } + + // Check if new app name already exists in either namespace + if _, err := os.Stat(newLocalDir); err == nil { + return fmt.Errorf("local app '%s' already exists", newAppName) + } else if !os.IsNotExist(err) { + return fmt.Errorf("failed to check if new local app exists: %w", err) + } + + if _, err := os.Stat(newDraftDir); err == nil { + return fmt.Errorf("draft app '%s' already exists", newAppName) + } else if !os.IsNotExist(err) { + return fmt.Errorf("failed to check if new draft app exists: %w", err) + } + + // Rename local app if it exists + if localExists { + if err := os.Rename(oldLocalDir, newLocalDir); err != nil { + return fmt.Errorf("failed to rename local app: %w", err) + } + } + + // Rename draft app if it exists + if draftExists { + if err := os.Rename(oldDraftDir, newDraftDir); err != nil { + // If local was renamed but draft fails, try to rollback local rename + if localExists { + if rollbackErr := os.Rename(newLocalDir, oldLocalDir); rollbackErr != nil { + return fmt.Errorf("failed to rename draft app (and failed to rollback local rename: %v): %w", rollbackErr, err) + } + } + return fmt.Errorf("failed to rename draft app: %w", err) + } + } + + return nil +}