package app import ( "fmt" "os" "path/filepath" "github.com/spf13/cobra" "gopkg.in/yaml.v3" "github.com/wild-cloud/wild-cli/internal/apps" "github.com/wild-cloud/wild-cli/internal/config" "github.com/wild-cloud/wild-cli/internal/environment" "github.com/wild-cloud/wild-cli/internal/output" ) func newAddCommand() *cobra.Command { return &cobra.Command{ Use: "add ", Short: "Add an application to the project", Long: `Add an application to the project with configuration. This copies the cached application template to your project's apps/ directory and creates initial configuration. Examples: wild app add nextcloud wild app add postgresql`, Args: cobra.ExactArgs(1), RunE: runAdd, } } func runAdd(cmd *cobra.Command, args []string) error { appName := args[0] output.Header("Adding Application") output.Info("App: " + appName) // Initialize environment env := environment.New() if err := env.RequiresProject(); err != nil { return err } // Create catalog catalog := apps.NewCatalog(env.CacheDir()) // Check if app is cached if !catalog.IsAppCached(appName) { output.Warning("App '" + appName + "' is not cached locally") output.Info("Run 'wild app fetch " + appName + "' first") return nil } // Check if app already exists in project appDir := filepath.Join(env.AppsDir(), appName) if _, err := os.Stat(appDir); err == nil { output.Warning("App '" + appName + "' already exists in project") output.Info("App directory: " + appDir) return nil } // Get app info app, err := catalog.FindApp(appName) if err != nil { return fmt.Errorf("getting app info: %w", err) } output.Info("Description: " + app.Description) output.Info("Version: " + app.Version) // Check dependencies if len(app.Requires) > 0 { output.Info("Dependencies: " + fmt.Sprintf("%v", app.Requires)) // Check if dependencies are available for _, dep := range app.Requires { depDir := filepath.Join(env.AppsDir(), dep) if _, err := os.Stat(depDir); os.IsNotExist(err) { output.Warning("Dependency '" + dep + "' not found in project") output.Info("Consider adding it first: wild app add " + dep) } else { output.Success("Dependency '" + dep + "' found") } } } // Copy app template from cache to project cacheDir := filepath.Join(env.CacheDir(), "apps", appName) if err := copyDir(cacheDir, appDir); err != nil { return fmt.Errorf("copying app template: %w", err) } // Create initial configuration in config.yaml if err := addAppConfig(env, appName, app); err != nil { output.Warning("Failed to add app configuration: " + err.Error()) } else { output.Success("Added default configuration to config.yaml") } output.Success("App '" + appName + "' added to project") output.Info("") output.Info("App directory: " + appDir) output.Info("Next steps:") output.Info(" wild config set apps." + appName + ".enabled true") output.Info(" wild app deploy " + appName) return nil } // copyDir recursively copies a directory func copyDir(src, dst string) error { entries, err := os.ReadDir(src) if err != nil { return err } if err := os.MkdirAll(dst, 0755); err != nil { return err } for _, entry := range entries { srcPath := filepath.Join(src, entry.Name()) dstPath := filepath.Join(dst, entry.Name()) if entry.IsDir() { if err := copyDir(srcPath, dstPath); err != nil { return err } } else { if err := copyFile(srcPath, dstPath); err != nil { return err } } } return nil } // copyFile copies a single file func copyFile(src, dst string) error { data, err := os.ReadFile(src) if err != nil { return err } return os.WriteFile(dst, data, 0644) } // addAppConfig adds default app configuration to config.yaml func addAppConfig(env *environment.Environment, appName string, app *apps.App) error { mgr := config.NewManager(env.ConfigPath(), env.SecretsPath()) // Create default app configuration appConfig := map[string]interface{}{ "enabled": false, "image": appName + ":latest", } // Add any default config from the app manifest if app.Config != nil { for key, value := range app.Config { appConfig[key] = value } } // Convert to YAML and set in config configData, err := yaml.Marshal(appConfig) if err != nil { return fmt.Errorf("marshaling config: %w", err) } configPath := "apps." + appName if err := mgr.Set(configPath, string(configData)); err != nil { return fmt.Errorf("setting config: %w", err) } return nil }