From f7161b293115ca397ad1ebc517ae0ad3673f4be3 Mon Sep 17 00:00:00 2001 From: Pedro Enrique Date: Wed, 21 May 2025 16:50:29 +0200 Subject: [PATCH 1/8] [AGENT-209] Refactor adding env vars from file --- cmd/cloud.go | 199 +---------------------- cmd/dev.go | 3 + cmd/env.go | 45 +----- cmd/project.go | 3 + cmd/root.go | 10 +- internal/envutil/envutil.go | 309 ++++++++++++++++++++++++++++++++++++ internal/util/api.go | 1 + internal/util/strings.go | 7 + 8 files changed, 333 insertions(+), 244 deletions(-) create mode 100644 internal/envutil/envutil.go diff --git a/cmd/cloud.go b/cmd/cloud.go index feb8da39..2fd77d8d 100644 --- a/cmd/cloud.go +++ b/cmd/cloud.go @@ -16,6 +16,7 @@ import ( "github.com/agentuity/cli/internal/agent" "github.com/agentuity/cli/internal/deployer" + "github.com/agentuity/cli/internal/envutil" "github.com/agentuity/cli/internal/errsystem" "github.com/agentuity/cli/internal/ignore" "github.com/agentuity/cli/internal/project" @@ -23,7 +24,6 @@ import ( "github.com/agentuity/go-common/crypto" "github.com/agentuity/go-common/env" "github.com/agentuity/go-common/logger" - cstr "github.com/agentuity/go-common/string" "github.com/agentuity/go-common/tui" "github.com/charmbracelet/lipgloss" "github.com/spf13/cobra" @@ -124,58 +124,6 @@ func ShowNewProjectImport(ctx context.Context, logger logger.Logger, cmd *cobra. var envTemplateFileNames = []string{".env.example", ".env.template"} -func readPossibleEnvTemplateFiles(baseDir string) map[string][]env.EnvLineComment { - var results map[string][]env.EnvLineComment - keys := make(map[string]bool) - for _, file := range envTemplateFileNames { - filename := filepath.Join(baseDir, file) - if !util.Exists(filename) { - continue - } - efc, err := env.ParseEnvFileWithComments(filename) - if err == nil { - if results == nil { - results = make(map[string][]env.EnvLineComment) - } - for _, ev := range efc { - if _, ok := keys[ev.Key]; !ok { - if isAgentuityEnv.MatchString(ev.Key) { - continue - } - keys[ev.Key] = true - results[file] = append(results[file], ev) - } - } - } - } - return results -} - -func appendToEnvFile(envfile string, envs []env.EnvLineComment) ([]env.EnvLineComment, error) { - le, err := env.ParseEnvFileWithComments(envfile) - if err != nil { - return nil, err - } - var buf strings.Builder - for _, ev := range le { - if ev.Comment != "" { - buf.WriteString(fmt.Sprintf("# %s\n", ev.Comment)) - } - buf.WriteString(fmt.Sprintf("%s=%s\n", ev.Key, ev.Raw)) - } - for _, ev := range envs { - if ev.Comment != "" { - buf.WriteString(fmt.Sprintf("# %s\n", ev.Comment)) - } - buf.WriteString(fmt.Sprintf("%s=%s\n", ev.Key, ev.Raw)) - le = append(le, ev) - } - if err := os.WriteFile(envfile, []byte(buf.String()), 0644); err != nil { - return nil, err - } - return le, nil -} - var border = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).Padding(1).BorderForeground(lipgloss.AdaptiveColor{Light: "#999999", Dark: "#999999"}) var redDiff = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#990000", Dark: "#EE0000"}) @@ -251,13 +199,11 @@ Examples: } var err error - var le []env.EnvLineComment var projectExists bool var action func() if !context.NewProject { action = func() { - var err error projectData, err = theproject.GetProject(ctx, logger, apiUrl, token) if err != nil { if err == project.ErrProjectNotFound { @@ -281,149 +227,8 @@ Examples: } ShowNewProjectImport(ctx, logger, cmd, apiUrl, token, projectId, theproject, dir, false) } - // check to see if we have any env vars that are not in the project - envfilename := filepath.Join(dir, ".env") - if tui.HasTTY && util.Exists(envfilename) { - - // attempt to see if we have any template files - templateEnvs := readPossibleEnvTemplateFiles(dir) - - le, err = env.ParseEnvFileWithComments(envfilename) - if err != nil { - errsystem.New(errsystem.ErrParseEnvironmentFile, err, - errsystem.WithContextMessage("Error parsing .env file")).ShowErrorAndExit() - } - envFile = &deployer.EnvFile{Filepath: envfilename, Env: le} - - if len(templateEnvs) > 0 { - kvmap := make(map[string]env.EnvLineComment) - for _, ev := range le { - if isAgentuityEnv.MatchString(ev.Key) { - continue - } - kvmap[ev.Key] = ev - } - var osenv map[string]string - var addtoenvfile []env.EnvLineComment - // look to see if we have any template environment variables that are not in the .env file - for filename, evs := range templateEnvs { - for _, ev := range evs { - if _, ok := kvmap[ev.Key]; !ok { - isSecret := looksLikeSecret.MatchString(ev.Key) - if !isSecret && descriptionLookingLikeASecret(ev.Comment) { - isSecret = true - } - _ = filename - var content string - var para []string - para = append(para, tui.Warning("Missing Environment Variable\n")) - para = append(para, fmt.Sprintf("The variable %s was found in %s but not in your %s file:\n", tui.Bold(ev.Key), tui.Bold(filename), tui.Bold(".env"))) - if ev.Comment != "" { - para = append(para, tui.Muted(fmt.Sprintf("# %s", ev.Comment))) - } - if isSecret { - para = append(para, redDiff.Render(fmt.Sprintf("+ %s=%s\n", ev.Key, cstr.Mask(ev.Val)))) - } else { - para = append(para, redDiff.Render(fmt.Sprintf("+ %s=%s\n", ev.Key, ev.Val))) - } - content = lipgloss.JoinVertical(lipgloss.Left, para...) - fmt.Println(border.Render(content)) - if !tui.Ask(logger, "Would you like to add it to your .env file?", true) { - fmt.Println() - tui.ShowWarning("cancelled") - continue - } - if osenv == nil { - osenv = loadOSEnv() - } - val := promptForEnv(logger, ev.Key, isSecret, nil, osenv, ev.Val, ev.Comment) - addtoenvfile = append(addtoenvfile, env.EnvLineComment{ - EnvLine: env.EnvLine{ - Key: ev.Key, - Val: val, - Raw: val, - }, - Comment: ev.Comment, - }) - } - } - } - if len(addtoenvfile) > 0 { - le, err = appendToEnvFile(envfilename, addtoenvfile) - if err != nil { - errsystem.New(errsystem.ErrParseEnvironmentFile, err, - errsystem.WithContextMessage("Error parsing .env file")).ShowErrorAndExit() - } - tui.ShowSuccess("added %s to your .env file", util.Pluralize(len(addtoenvfile), "environment variable", "environment variables")) - fmt.Println() - } - } - - var foundkeys []string - for _, ev := range le { - if isAgentuityEnv.MatchString(ev.Key) { - continue - } - if projectData != nil && projectData.Env != nil && projectData.Env[ev.Key] == ev.Val { - continue - } - if projectData != nil && projectData.Secrets != nil && projectData.Secrets[ev.Key] == cstr.Mask(ev.Val) { - continue - } - foundkeys = append(foundkeys, ev.Key) - } - if len(foundkeys) > 0 { - var title string - var suffix string - switch { - case len(foundkeys) < 3 && len(foundkeys) > 1: - suffix = "it" - var colorized []string - for _, key := range foundkeys { - colorized = append(colorized, tui.Bold(key)) - } - title = fmt.Sprintf("The environment variables %s from %s are not been set in the project.", strings.Join(colorized, ", "), tui.Bold(".env")) - case len(foundkeys) == 1: - suffix = "it" - title = fmt.Sprintf("The environment variable %s from %s has not been set in the project.", tui.Bold(foundkeys[0]), tui.Bold(".env")) - default: - suffix = "them" - title = fmt.Sprintf("There are %d environment variables from %s that are not set in the project.", len(foundkeys), tui.Bold(".env")) - } - fmt.Println(title) - if !tui.Ask(logger, "Would you like to set "+suffix+"?", true) { - fmt.Println() - tui.ShowWarning("cancelled") - return - } - envs, secrets := loadEnvFile(le, false) - pd, err := theproject.SetProjectEnv(ctx, logger, apiUrl, token, envs, secrets) - if err != nil { - if isCancelled(ctx) { - os.Exit(1) - } - errsystem.New(errsystem.ErrEnvironmentVariablesNotSet, err, - errsystem.WithContextMessage("Failed to set project environment variables")).ShowErrorAndExit() - } - fmt.Println() - fmt.Println() - projectData = pd // overwrite with the new version - switch { - case len(envs) > 0 && len(secrets) > 0: - tui.ShowSuccess("Environment variables and secrets added") - case len(envs) == 1: - tui.ShowSuccess("Environment variable added") - case len(envs) > 1: - tui.ShowSuccess("Environment variables added") - case len(secrets) == 1: - tui.ShowSuccess("Secret added") - case len(secrets) > 1: - tui.ShowSuccess("Secrets added") - } - fmt.Println() - } - } + envFile, projectData = envutil.ProcessEnvFiles(ctx, logger, dir, theproject, projectData, apiUrl, token) if tui.HasTTY { _, localIssues, remoteIssues, err := buildAgentTree(keys, state, context) diff --git a/cmd/dev.go b/cmd/dev.go index 430c74fb..9233dd24 100644 --- a/cmd/dev.go +++ b/cmd/dev.go @@ -12,6 +12,7 @@ import ( "github.com/agentuity/cli/internal/bundler" "github.com/agentuity/cli/internal/dev" + "github.com/agentuity/cli/internal/envutil" "github.com/agentuity/cli/internal/errsystem" "github.com/agentuity/cli/internal/project" "github.com/agentuity/cli/internal/util" @@ -66,6 +67,8 @@ Examples: errsystem.New(errsystem.ErrInvalidConfiguration, err, errsystem.WithUserMessage("Failed to validate project (%s) using the provided API key from the .env file in %s. This is most likely due to the API key being invalid or the project has been deleted.\n\nYou can import this project using the following command:\n\n"+tui.Command("project import"), theproject.Project.ProjectId, dir), errsystem.WithContextMessage(fmt.Sprintf("Failed to get project: %s", err))).ShowErrorAndExit() } + _, project = envutil.ProcessEnvFiles(ctx, log, dir, theproject.Project, project, theproject.APIURL, apiKey) + orgId := project.OrgId port, _ := cmd.Flags().GetInt("port") diff --git a/cmd/env.go b/cmd/env.go index a0077ab1..5859a9f6 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -12,10 +12,11 @@ import ( "strings" "syscall" + "github.com/agentuity/cli/internal/envutil" "github.com/agentuity/cli/internal/errsystem" "github.com/agentuity/cli/internal/project" + "github.com/agentuity/cli/internal/util" "github.com/agentuity/go-common/env" - "github.com/agentuity/go-common/logger" cstr "github.com/agentuity/go-common/string" "github.com/agentuity/go-common/sys" "github.com/agentuity/go-common/tui" @@ -74,40 +75,6 @@ func loadEnvFile(le []env.EnvLineComment, forceSecret bool) (map[string]string, return envs, secrets } -func promptForEnv(logger logger.Logger, key string, isSecret bool, localenv map[string]string, osenv map[string]string, defaultValue string, placeholder string) string { - prompt := "Enter the value for " + key - var help string - var value string - if isSecret { - prompt = "Enter the secret value for " + key - if val, ok := localenv[key]; ok { - help = "Press enter to set as " + maxString(cstr.Mask(val), 30) + " from your .env file" - if defaultValue == "" { - defaultValue = val - } - } else if val, ok := osenv[key]; ok { - help = "Press enter to set as " + maxString(cstr.Mask(val), 30) + " from your environment" - if defaultValue == "" { - defaultValue = val - } - } else { - help = "Your input will be masked" - } - value = tui.Password(logger, prompt, help) - } else { - if placeholder == "" { - value = tui.InputWithPlaceholder(logger, prompt, placeholder, defaultValue) - } else { - value = tui.InputWithPlaceholder(logger, prompt, help, defaultValue) - } - } - - if value == "" && defaultValue != "" { - value = defaultValue - } - return value -} - func loadOSEnv() map[string]string { osenv := make(map[string]string) for _, line := range os.Environ() { @@ -251,16 +218,16 @@ Examples: } } if value == "" { - value = promptForEnv(logger, key, isSecret, localenv, osenv, "", "") + value = envutil.PromptForEnv(logger, key, isSecret, localenv, osenv, "", "") } } if key != "" && value != "" { if isSecret { secrets[key] = value - tui.ShowSuccess("%s=%s", key, maxString(cstr.Mask(value), 40)) + tui.ShowSuccess("%s=%s", key, util.MaxString(cstr.Mask(value), 40)) } else { envs[key] = value - tui.ShowSuccess("%s=%s", key, maxString(value, 40)) + tui.ShowSuccess("%s=%s", key, util.MaxString(value, 40)) } } if askMore { @@ -426,7 +393,7 @@ Examples: if !hasTTY { fmt.Printf("%s=%s\n", key, value) } else { - fmt.Printf("%s=%s\n", tui.Title(key), tui.Muted(maxString(value, 40))) + fmt.Printf("%s=%s\n", tui.Title(key), tui.Muted(util.MaxString(value, 40))) } } if len(projectData.Env) == 0 && len(projectData.Secrets) == 0 { diff --git a/cmd/project.go b/cmd/project.go index 67cd1fb6..9f29c3f5 100644 --- a/cmd/project.go +++ b/cmd/project.go @@ -11,6 +11,7 @@ import ( "sort" "syscall" + "github.com/agentuity/cli/internal/envutil" "github.com/agentuity/cli/internal/errsystem" "github.com/agentuity/cli/internal/mcp" "github.com/agentuity/cli/internal/organization" @@ -821,6 +822,8 @@ Examples: ShowNewProjectImport(ctx, logger, cmd, context.APIURL, context.Token, "", context.Project, context.Dir, true) + _, _ = envutil.ProcessEnvFiles(ctx, logger, context.Dir, context.Project, nil, context.APIURL, context.Token) + }, } diff --git a/cmd/root.go b/cmd/root.go index edf1ade3..91972a53 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,6 +12,7 @@ import ( "time" "github.com/agentuity/cli/internal/deployer" + "github.com/agentuity/cli/internal/envutil" "github.com/agentuity/cli/internal/errsystem" "github.com/agentuity/cli/internal/templates" "github.com/agentuity/cli/internal/util" @@ -168,13 +169,6 @@ func initConfig() { } } -func maxString(val string, max int) string { - if len(val) > max { - return val[:max] + "..." - } - return val -} - func initScreenWithLogo() { tui.ClearScreen() tui.Logo() @@ -189,7 +183,7 @@ func createPromptHelper() deployer.PromptHelpers { PrintLock: tui.ShowLock, PrintWarning: tui.ShowWarning, Ask: tui.Ask, - PromptForEnv: promptForEnv, + PromptForEnv: envutil.PromptForEnv, } } diff --git a/internal/envutil/envutil.go b/internal/envutil/envutil.go new file mode 100644 index 00000000..b1921934 --- /dev/null +++ b/internal/envutil/envutil.go @@ -0,0 +1,309 @@ +package envutil + +import ( + "context" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/agentuity/cli/internal/deployer" + "github.com/agentuity/cli/internal/errsystem" + "github.com/agentuity/cli/internal/project" + util "github.com/agentuity/cli/internal/util" + "github.com/agentuity/go-common/env" + "github.com/agentuity/go-common/logger" + cstr "github.com/agentuity/go-common/string" + "github.com/agentuity/go-common/tui" + "github.com/charmbracelet/lipgloss" +) + +var EnvTemplateFileNames = []string{".env.example", ".env.template"} + +var border = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).Padding(1).BorderForeground(lipgloss.AdaptiveColor{Light: "#999999", Dark: "#999999"}) +var redDiff = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#990000", Dark: "#EE0000"}) + +var LooksLikeSecret = looksLikeSecret +var IsAgentuityEnv = isAgentuityEnv + +var looksLikeSecret = regexp.MustCompile(`(?i)KEY|SECRET|TOKEN|PASSWORD|sk_`) +var isAgentuityEnv = regexp.MustCompile(`(?i)AGENTUITY_`) + +// ProcessEnvFiles handles .env and template env processing +func ProcessEnvFiles(ctx context.Context, logger logger.Logger, dir string, theproject *project.Project, projectData *project.ProjectData, apiUrl, token string) (*deployer.EnvFile, *project.ProjectData) { + envfilename := filepath.Join(dir, ".env") + var envFile *deployer.EnvFile + if tui.HasTTY && util.Exists(envfilename) { + // attempt to see if we have any template files + templateEnvs := ReadPossibleEnvTemplateFiles(dir) + + le, err := env.ParseEnvFileWithComments(envfilename) + if err != nil { + errsystem.New(errsystem.ErrParseEnvironmentFile, err, + errsystem.WithContextMessage("Error parsing .env file")).ShowErrorAndExit() + } + envFile = &deployer.EnvFile{Filepath: envfilename, Env: le} + + le, err = HandleMissingTemplateEnvs(logger, dir, envfilename, le, templateEnvs) + if err != nil { + errsystem.New(errsystem.ErrParseEnvironmentFile, err, + errsystem.WithContextMessage("Error parsing .env file")).ShowErrorAndExit() + } + + projectData = HandleMissingProjectEnvs(ctx, logger, le, projectData, theproject, apiUrl, token) + return envFile, projectData + } + return envFile, projectData +} + +// HandleMissingTemplateEnvs handles missing envs from template files +func HandleMissingTemplateEnvs(logger logger.Logger, dir, envfilename string, le []env.EnvLineComment, templateEnvs map[string][]env.EnvLineComment) ([]env.EnvLineComment, error) { + if len(templateEnvs) == 0 { + return le, nil + } + kvmap := make(map[string]env.EnvLineComment) + for _, ev := range le { + if isAgentuityEnv.MatchString(ev.Key) { + continue + } + kvmap[ev.Key] = ev + } + var osenv map[string]string + var addtoenvfile []env.EnvLineComment + // look to see if we have any template environment variables that are not in the .env file + for filename, evs := range templateEnvs { + for _, ev := range evs { + if _, ok := kvmap[ev.Key]; !ok { + isSecret := looksLikeSecret.MatchString(ev.Key) + if !isSecret && DescriptionLookingLikeASecret(ev.Comment) { + isSecret = true + } + _ = filename + var content string + var para []string + para = append(para, tui.Warning("Missing Environment Variable\n")) + para = append(para, fmt.Sprintf("The variable %s was found in %s but not in your %s file:\n", tui.Bold(ev.Key), tui.Bold(filename), tui.Bold(".env"))) + if ev.Comment != "" { + para = append(para, tui.Muted(fmt.Sprintf("# %s", ev.Comment))) + } + if isSecret { + para = append(para, redDiff.Render(fmt.Sprintf("+ %s=%s\n", ev.Key, cstr.Mask(ev.Val)))) + } else { + para = append(para, redDiff.Render(fmt.Sprintf("+ %s=%s\n", ev.Key, ev.Val))) + } + content = lipgloss.JoinVertical(lipgloss.Left, para...) + fmt.Println(border.Render(content)) + if !tui.Ask(logger, "Would you like to add it to your .env file?", true) { + fmt.Println() + tui.ShowWarning("cancelled") + continue + } + if osenv == nil { + osenv = LoadOSEnv() + } + val := PromptForEnv(logger, ev.Key, isSecret, nil, osenv, ev.Val, ev.Comment) + addtoenvfile = append(addtoenvfile, env.EnvLineComment{ + EnvLine: env.EnvLine{ + Key: ev.Key, + Val: val, + Raw: val, + }, + Comment: ev.Comment, + }) + } + } + } + if len(addtoenvfile) > 0 { + var err error + le, err = AppendToEnvFile(envfilename, addtoenvfile) + if err != nil { + return le, err + } + tui.ShowSuccess("added %s to your .env file", util.Pluralize(len(addtoenvfile), "environment variable", "environment variables")) + fmt.Println() + } + return le, nil +} + +// HandleMissingProjectEnvs handles missing envs in project +func HandleMissingProjectEnvs(ctx context.Context, logger logger.Logger, le []env.EnvLineComment, projectData *project.ProjectData, theproject *project.Project, apiUrl, token string) *project.ProjectData { + keyvalue := map[string]string{} + for _, ev := range le { + if isAgentuityEnv.MatchString(ev.Key) { + continue + } + if projectData != nil && projectData.Env != nil && projectData.Env[ev.Key] == ev.Val { + continue + } + if projectData != nil && projectData.Secrets != nil && projectData.Secrets[ev.Key] == cstr.Mask(ev.Val) { + continue + } + keyvalue[ev.Key] = ev.Val + } + if len(keyvalue) > 0 { + var title string + var suffix string + switch { + case len(keyvalue) < 3 && len(keyvalue) > 1: + suffix = "it" + var colorized []string + for _, key := range keyvalue { + colorized = append(colorized, tui.Bold(key)) + } + title = fmt.Sprintf("The environment variables %s from %s are not been set in the project.", strings.Join(colorized, ", "), tui.Bold(".env")) + case len(keyvalue) == 1: + var key string + for _key, _ := range keyvalue { + key = _key + } + suffix = "it" + title = fmt.Sprintf("The environment variable %s from %s has not been set in the project.", tui.Bold(key), tui.Bold(".env")) + default: + suffix = "them" + title = fmt.Sprintf("There are %d environment variables from %s that are not set in the project.", len(keyvalue), tui.Bold(".env")) + } + if tui.Ask(logger, title+"\nWould you like to set "+suffix+" now?", true) { + for key, val := range keyvalue { + if projectData.Env == nil { + projectData.Env = make(map[string]string) + } + projectData.Env[key] = val + } + _, err := theproject.SetProjectEnv(ctx, logger, apiUrl, token, projectData.Env, projectData.Secrets) + if err != nil { + errsystem.New(errsystem.ErrApiRequest, err, errsystem.WithUserMessage("Failed to save project settings")).ShowErrorAndExit() + } + } + } + return projectData +} + +// ReadPossibleEnvTemplateFiles reads .env.example and .env.template files +func ReadPossibleEnvTemplateFiles(baseDir string) map[string][]env.EnvLineComment { + var results map[string][]env.EnvLineComment + keys := make(map[string]bool) + for _, file := range EnvTemplateFileNames { + filename := filepath.Join(baseDir, file) + if !util.Exists(filename) { + continue + } + if efc, err := env.ParseEnvFileWithComments(filename); err == nil { + if results == nil { + results = make(map[string][]env.EnvLineComment) + } + for _, ev := range efc { + if _, ok := keys[ev.Key]; !ok { + if isAgentuityEnv.MatchString(ev.Key) { + continue + } + keys[ev.Key] = true + results[file] = append(results[file], ev) + } + } + } else { + errsystem.New(errsystem.ErrParseEnvironmentFile, err, + errsystem.WithContextMessage("Error parsing .env file")).ShowErrorAndExit() + } + } + return results +} + +// AppendToEnvFile appends envs to the .env file +func AppendToEnvFile(envfile string, envs []env.EnvLineComment) ([]env.EnvLineComment, error) { + le, err := env.ParseEnvFileWithComments(envfile) + if err != nil { + return nil, err + } + var buf strings.Builder + for _, ev := range le { + if ev.Comment != "" { + buf.WriteString(fmt.Sprintf("# %s\n", ev.Comment)) + } + buf.WriteString(fmt.Sprintf("%s=%s\n", ev.Key, ev.Raw)) + } + for _, ev := range envs { + if ev.Comment != "" { + buf.WriteString(fmt.Sprintf("# %s\n", ev.Comment)) + } + buf.WriteString(fmt.Sprintf("%s=%s\n", ev.Key, ev.Raw)) + le = append(le, ev) + } + if err := os.WriteFile(envfile, []byte(buf.String()), 0644); err != nil { + return nil, err + } + return le, nil +} + +// Utility functions needed for env processing + +// DescriptionLookingLikeASecret checks if a description looks like a secret +func DescriptionLookingLikeASecret(description string) bool { + if description == "" { + return false + } + val := strings.ToLower(description) + if strings.Contains(val, "secret") { + return true + } + if strings.Contains(val, "password") { + return true + } + if strings.Contains(val, "key") { + return true + } + return false +} + +// LoadOSEnv loads the OS environment variables +func LoadOSEnv() map[string]string { + osenv := make(map[string]string) + for _, line := range os.Environ() { + parts := strings.SplitN(line, "=", 2) + if len(parts) == 2 && !isAgentuityEnv.MatchString(parts[0]) { + osenv[parts[0]] = parts[1] + } + } + return osenv +} + +func maxString(val string, max int) string { + if len(val) > max { + return val[:max] + "..." + } + return val +} + +func PromptForEnv(logger logger.Logger, key string, isSecret bool, localenv map[string]string, osenv map[string]string, defaultValue string, placeholder string) string { + prompt := "Enter the value for " + key + var help string + var value string + if isSecret { + prompt = "Enter the secret value for " + key + if val, ok := localenv[key]; ok { + help = "Press enter to set as " + maxString(cstr.Mask(val), 30) + " from your .env file" + if defaultValue == "" { + defaultValue = val + } + } else if val, ok := osenv[key]; ok { + help = "Press enter to set as " + maxString(cstr.Mask(val), 30) + " from your environment" + if defaultValue == "" { + defaultValue = val + } + } else { + help = "Your input will be masked" + } + value = tui.Password(logger, prompt, help) + } else { + if placeholder == "" { + value = tui.InputWithPlaceholder(logger, prompt, help, defaultValue) + } else { + value = tui.InputWithPlaceholder(logger, prompt, placeholder, defaultValue) + } + } + + if value == "" && defaultValue != "" { + value = defaultValue + } + return value +} diff --git a/internal/util/api.go b/internal/util/api.go index 313537f7..45bfcb0c 100644 --- a/internal/util/api.go +++ b/internal/util/api.go @@ -155,6 +155,7 @@ func (c *APIClient) Do(method, pathParam string, payload interface{}, response i if err != nil { return NewAPIError(u.String(), method, 0, "", fmt.Errorf("error creating request: %w", err), traceID) } + req.Header.Set("User-Agent", UserAgent()) req.Header.Set("Content-Type", "application/json") if c.token != "" { diff --git a/internal/util/strings.go b/internal/util/strings.go index 89067dc5..de497b50 100644 --- a/internal/util/strings.go +++ b/internal/util/strings.go @@ -38,3 +38,10 @@ func Pluralize(count int, singular string, plural string) string { } return fmt.Sprintf("%d %s", count, plural) } + +func MaxString(val string, max int) string { + if len(val) > max { + return val[:max] + "..." + } + return val +} From 97bdbad656fd54823789a82607ea3f79c1691c1a Mon Sep 17 00:00:00 2001 From: Pedro Enrique Date: Wed, 21 May 2025 19:38:23 +0200 Subject: [PATCH 2/8] Force import env vars --- cmd/cloud.go | 10 +++- cmd/dev.go | 8 ++- cmd/project.go | 9 ++- internal/envutil/envutil.go | 113 ++++++++++++++++++++---------------- 4 files changed, 84 insertions(+), 56 deletions(-) diff --git a/cmd/cloud.go b/cmd/cloud.go index 2fd77d8d..f32da434 100644 --- a/cmd/cloud.go +++ b/cmd/cloud.go @@ -227,8 +227,13 @@ Examples: } ShowNewProjectImport(ctx, logger, cmd, apiUrl, token, projectId, theproject, dir, false) } + + force, _ := cmd.Flags().GetBool("force-env") + if !tui.HasTTY { + force = true + } // check to see if we have any env vars that are not in the project - envFile, projectData = envutil.ProcessEnvFiles(ctx, logger, dir, theproject, projectData, apiUrl, token) + envFile, projectData = envutil.ProcessEnvFiles(ctx, logger, dir, theproject, projectData, apiUrl, token, force) if tui.HasTTY { _, localIssues, remoteIssues, err := buildAgentTree(keys, state, context) @@ -632,6 +637,7 @@ func init() { cloudDeployCmd.Flags().StringArray("tag", nil, "Tag(s) to associate with this deployment (can be specified multiple times)") cloudDeployCmd.Flags().String("description", "", "Description for the deployment") cloudDeployCmd.Flags().String("message", "", "A shorter description for the deployment") + cloudDeployCmd.Flags().Bool("force-env", false, "Force the processing of environment files") cloudDeployCmd.Flags().MarkHidden("deploymentId") cloudDeployCmd.Flags().MarkHidden("ci") @@ -641,7 +647,7 @@ func init() { cloudDeployCmd.Flags().MarkHidden("ci-message") cloudDeployCmd.Flags().MarkHidden("ci-git-provider") cloudDeployCmd.Flags().MarkHidden("ci-logs-url") - + cloudDeployCmd.Flags().MarkHidden("force-env") cloudDeployCmd.Flags().String("format", "text", "The output format to use for results which can be either 'text' or 'json'") cloudDeployCmd.Flags().String("org-id", "", "The organization to create the project in") cloudDeployCmd.Flags().String("templates-dir", "", "The directory to load the templates. Defaults to loading them from the github.com/agentuity/templates repository") diff --git a/cmd/dev.go b/cmd/dev.go index 9233dd24..2f00e041 100644 --- a/cmd/dev.go +++ b/cmd/dev.go @@ -67,7 +67,11 @@ Examples: errsystem.New(errsystem.ErrInvalidConfiguration, err, errsystem.WithUserMessage("Failed to validate project (%s) using the provided API key from the .env file in %s. This is most likely due to the API key being invalid or the project has been deleted.\n\nYou can import this project using the following command:\n\n"+tui.Command("project import"), theproject.Project.ProjectId, dir), errsystem.WithContextMessage(fmt.Sprintf("Failed to get project: %s", err))).ShowErrorAndExit() } - _, project = envutil.ProcessEnvFiles(ctx, log, dir, theproject.Project, project, theproject.APIURL, apiKey) + force, _ := cmd.Flags().GetBool("force-env") + if !tui.HasTTY { + force = true + } + _, project = envutil.ProcessEnvFiles(ctx, log, dir, theproject.Project, project, theproject.APIURL, apiKey, force) orgId := project.OrgId @@ -278,4 +282,6 @@ func init() { devCmd.Flags().Int("port", 0, "The port to run the development server on (uses project default if not provided)") devCmd.Flags().String("server", "echo.agentuity.cloud", "the echo server to connect to") devCmd.Flags().MarkHidden("server") + devCmd.Flags().Bool("force-env", false, "Force the processing of environment files") + devCmd.Flags().MarkHidden("force-env") } diff --git a/cmd/project.go b/cmd/project.go index 9f29c3f5..436bbf8c 100644 --- a/cmd/project.go +++ b/cmd/project.go @@ -821,8 +821,11 @@ Examples: } ShowNewProjectImport(ctx, logger, cmd, context.APIURL, context.Token, "", context.Project, context.Dir, true) - - _, _ = envutil.ProcessEnvFiles(ctx, logger, context.Dir, context.Project, nil, context.APIURL, context.Token) + force, _ := cmd.Flags().GetBool("force-env") + if !tui.HasTTY { + force = true + } + _, _ = envutil.ProcessEnvFiles(ctx, logger, context.Dir, context.Project, nil, context.APIURL, context.Token, force) }, } @@ -871,6 +874,8 @@ func init() { projectImportCmd.Flags().String("apikey", "", "The API key to use for the project") projectImportCmd.Flags().String("name", "", "The name of the project to import") projectImportCmd.Flags().String("description", "", "The description of the project to import") + projectImportCmd.Flags().Bool("force-env", false, "Force the processing of environment files") + projectImportCmd.Flags().MarkHidden("force-env") projectImportCmd.Flags().MarkHidden("apikey") projectImportCmd.Flags().MarkHidden("name") diff --git a/internal/envutil/envutil.go b/internal/envutil/envutil.go index b1921934..89e44471 100644 --- a/internal/envutil/envutil.go +++ b/internal/envutil/envutil.go @@ -31,10 +31,10 @@ var looksLikeSecret = regexp.MustCompile(`(?i)KEY|SECRET|TOKEN|PASSWORD|sk_`) var isAgentuityEnv = regexp.MustCompile(`(?i)AGENTUITY_`) // ProcessEnvFiles handles .env and template env processing -func ProcessEnvFiles(ctx context.Context, logger logger.Logger, dir string, theproject *project.Project, projectData *project.ProjectData, apiUrl, token string) (*deployer.EnvFile, *project.ProjectData) { +func ProcessEnvFiles(ctx context.Context, logger logger.Logger, dir string, theproject *project.Project, projectData *project.ProjectData, apiUrl, token string, force bool) (*deployer.EnvFile, *project.ProjectData) { envfilename := filepath.Join(dir, ".env") var envFile *deployer.EnvFile - if tui.HasTTY && util.Exists(envfilename) { + if (tui.HasTTY || force) && util.Exists(envfilename) { // attempt to see if we have any template files templateEnvs := ReadPossibleEnvTemplateFiles(dir) @@ -45,20 +45,20 @@ func ProcessEnvFiles(ctx context.Context, logger logger.Logger, dir string, thep } envFile = &deployer.EnvFile{Filepath: envfilename, Env: le} - le, err = HandleMissingTemplateEnvs(logger, dir, envfilename, le, templateEnvs) + le, err = HandleMissingTemplateEnvs(logger, dir, envfilename, le, templateEnvs, force) if err != nil { errsystem.New(errsystem.ErrParseEnvironmentFile, err, errsystem.WithContextMessage("Error parsing .env file")).ShowErrorAndExit() } - projectData = HandleMissingProjectEnvs(ctx, logger, le, projectData, theproject, apiUrl, token) + projectData = HandleMissingProjectEnvs(ctx, logger, le, projectData, theproject, apiUrl, token, force) return envFile, projectData } return envFile, projectData } // HandleMissingTemplateEnvs handles missing envs from template files -func HandleMissingTemplateEnvs(logger logger.Logger, dir, envfilename string, le []env.EnvLineComment, templateEnvs map[string][]env.EnvLineComment) ([]env.EnvLineComment, error) { +func HandleMissingTemplateEnvs(logger logger.Logger, dir, envfilename string, le []env.EnvLineComment, templateEnvs map[string][]env.EnvLineComment, force bool) ([]env.EnvLineComment, error) { if len(templateEnvs) == 0 { return le, nil } @@ -79,35 +79,41 @@ func HandleMissingTemplateEnvs(logger logger.Logger, dir, envfilename string, le if !isSecret && DescriptionLookingLikeASecret(ev.Comment) { isSecret = true } - _ = filename - var content string - var para []string - para = append(para, tui.Warning("Missing Environment Variable\n")) - para = append(para, fmt.Sprintf("The variable %s was found in %s but not in your %s file:\n", tui.Bold(ev.Key), tui.Bold(filename), tui.Bold(".env"))) - if ev.Comment != "" { - para = append(para, tui.Muted(fmt.Sprintf("# %s", ev.Comment))) - } - if isSecret { - para = append(para, redDiff.Render(fmt.Sprintf("+ %s=%s\n", ev.Key, cstr.Mask(ev.Val)))) - } else { - para = append(para, redDiff.Render(fmt.Sprintf("+ %s=%s\n", ev.Key, ev.Val))) + if !force { + var content string + var para []string + para = append(para, tui.Warning("Missing Environment Variable\n")) + para = append(para, fmt.Sprintf("The variable %s was found in %s but not in your %s file:\n", tui.Bold(ev.Key), tui.Bold(filename), tui.Bold(".env"))) + if ev.Comment != "" { + para = append(para, tui.Muted(fmt.Sprintf("# %s", ev.Comment))) + } + if isSecret { + para = append(para, redDiff.Render(fmt.Sprintf("+ %s=%s\n", ev.Key, cstr.Mask(ev.Val)))) + } else { + para = append(para, redDiff.Render(fmt.Sprintf("+ %s=%s\n", ev.Key, ev.Val))) + } + content = lipgloss.JoinVertical(lipgloss.Left, para...) + fmt.Println(border.Render(content)) + if !tui.Ask(logger, "Would you like to add it to your .env file?", true) { + fmt.Println() + tui.ShowWarning("cancelled") + continue + } + if osenv == nil { + osenv = LoadOSEnv() + } + if ev.Val == "" { + ev.Val = PromptForEnv(logger, ev.Key, isSecret, nil, osenv, ev.Val, ev.Comment) + } } - content = lipgloss.JoinVertical(lipgloss.Left, para...) - fmt.Println(border.Render(content)) - if !tui.Ask(logger, "Would you like to add it to your .env file?", true) { - fmt.Println() - tui.ShowWarning("cancelled") + if ev.Val != "" { continue } - if osenv == nil { - osenv = LoadOSEnv() - } - val := PromptForEnv(logger, ev.Key, isSecret, nil, osenv, ev.Val, ev.Comment) addtoenvfile = append(addtoenvfile, env.EnvLineComment{ EnvLine: env.EnvLine{ Key: ev.Key, - Val: val, - Raw: val, + Val: ev.Val, + Raw: ev.Raw, }, Comment: ev.Comment, }) @@ -120,14 +126,16 @@ func HandleMissingTemplateEnvs(logger logger.Logger, dir, envfilename string, le if err != nil { return le, err } - tui.ShowSuccess("added %s to your .env file", util.Pluralize(len(addtoenvfile), "environment variable", "environment variables")) - fmt.Println() + if tui.HasTTY { + tui.ShowSuccess("added %s to your .env file", util.Pluralize(len(addtoenvfile), "environment variable", "environment variables")) + fmt.Println() + } } return le, nil } // HandleMissingProjectEnvs handles missing envs in project -func HandleMissingProjectEnvs(ctx context.Context, logger logger.Logger, le []env.EnvLineComment, projectData *project.ProjectData, theproject *project.Project, apiUrl, token string) *project.ProjectData { +func HandleMissingProjectEnvs(ctx context.Context, logger logger.Logger, le []env.EnvLineComment, projectData *project.ProjectData, theproject *project.Project, apiUrl, token string, force bool) *project.ProjectData { keyvalue := map[string]string{} for _, ev := range le { if isAgentuityEnv.MatchString(ev.Key) { @@ -142,28 +150,31 @@ func HandleMissingProjectEnvs(ctx context.Context, logger logger.Logger, le []en keyvalue[ev.Key] = ev.Val } if len(keyvalue) > 0 { - var title string - var suffix string - switch { - case len(keyvalue) < 3 && len(keyvalue) > 1: - suffix = "it" - var colorized []string - for _, key := range keyvalue { - colorized = append(colorized, tui.Bold(key)) - } - title = fmt.Sprintf("The environment variables %s from %s are not been set in the project.", strings.Join(colorized, ", "), tui.Bold(".env")) - case len(keyvalue) == 1: - var key string - for _key, _ := range keyvalue { - key = _key + if !force { + var title string + var suffix string + switch { + case len(keyvalue) < 3 && len(keyvalue) > 1: + suffix = "it" + var colorized []string + for _, key := range keyvalue { + colorized = append(colorized, tui.Bold(key)) + } + title = fmt.Sprintf("The environment variables %s from %s are not been set in the project.", strings.Join(colorized, ", "), tui.Bold(".env")) + case len(keyvalue) == 1: + var key string + for _key, _ := range keyvalue { + key = _key + } + suffix = "it" + title = fmt.Sprintf("The environment variable %s from %s has not been set in the project.", tui.Bold(key), tui.Bold(".env")) + default: + suffix = "them" + title = fmt.Sprintf("There are %d environment variables from %s that are not set in the project.", len(keyvalue), tui.Bold(".env")) } - suffix = "it" - title = fmt.Sprintf("The environment variable %s from %s has not been set in the project.", tui.Bold(key), tui.Bold(".env")) - default: - suffix = "them" - title = fmt.Sprintf("There are %d environment variables from %s that are not set in the project.", len(keyvalue), tui.Bold(".env")) + force = tui.Ask(logger, title+"\nWould you like to set "+suffix+" now?", true) } - if tui.Ask(logger, title+"\nWould you like to set "+suffix+" now?", true) { + if force { for key, val := range keyvalue { if projectData.Env == nil { projectData.Env = make(map[string]string) From ba077815617f44c54c1107f4488db0f306e0a798 Mon Sep 17 00:00:00 2001 From: Pedro Enrique Date: Wed, 21 May 2025 19:42:31 +0200 Subject: [PATCH 3/8] just force --- cmd/cloud.go | 6 +++--- cmd/dev.go | 5 ++--- cmd/project.go | 5 ++--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/cmd/cloud.go b/cmd/cloud.go index f32da434..d212ba09 100644 --- a/cmd/cloud.go +++ b/cmd/cloud.go @@ -228,7 +228,7 @@ Examples: ShowNewProjectImport(ctx, logger, cmd, apiUrl, token, projectId, theproject, dir, false) } - force, _ := cmd.Flags().GetBool("force-env") + force, _ := cmd.Flags().GetBool("force") if !tui.HasTTY { force = true } @@ -637,7 +637,7 @@ func init() { cloudDeployCmd.Flags().StringArray("tag", nil, "Tag(s) to associate with this deployment (can be specified multiple times)") cloudDeployCmd.Flags().String("description", "", "Description for the deployment") cloudDeployCmd.Flags().String("message", "", "A shorter description for the deployment") - cloudDeployCmd.Flags().Bool("force-env", false, "Force the processing of environment files") + cloudDeployCmd.Flags().Bool("force", false, "Force the processing of environment files") cloudDeployCmd.Flags().MarkHidden("deploymentId") cloudDeployCmd.Flags().MarkHidden("ci") @@ -647,7 +647,7 @@ func init() { cloudDeployCmd.Flags().MarkHidden("ci-message") cloudDeployCmd.Flags().MarkHidden("ci-git-provider") cloudDeployCmd.Flags().MarkHidden("ci-logs-url") - cloudDeployCmd.Flags().MarkHidden("force-env") + cloudDeployCmd.Flags().String("format", "text", "The output format to use for results which can be either 'text' or 'json'") cloudDeployCmd.Flags().String("org-id", "", "The organization to create the project in") cloudDeployCmd.Flags().String("templates-dir", "", "The directory to load the templates. Defaults to loading them from the github.com/agentuity/templates repository") diff --git a/cmd/dev.go b/cmd/dev.go index 2f00e041..3e00f1bf 100644 --- a/cmd/dev.go +++ b/cmd/dev.go @@ -67,7 +67,7 @@ Examples: errsystem.New(errsystem.ErrInvalidConfiguration, err, errsystem.WithUserMessage("Failed to validate project (%s) using the provided API key from the .env file in %s. This is most likely due to the API key being invalid or the project has been deleted.\n\nYou can import this project using the following command:\n\n"+tui.Command("project import"), theproject.Project.ProjectId, dir), errsystem.WithContextMessage(fmt.Sprintf("Failed to get project: %s", err))).ShowErrorAndExit() } - force, _ := cmd.Flags().GetBool("force-env") + force, _ := cmd.Flags().GetBool("force") if !tui.HasTTY { force = true } @@ -282,6 +282,5 @@ func init() { devCmd.Flags().Int("port", 0, "The port to run the development server on (uses project default if not provided)") devCmd.Flags().String("server", "echo.agentuity.cloud", "the echo server to connect to") devCmd.Flags().MarkHidden("server") - devCmd.Flags().Bool("force-env", false, "Force the processing of environment files") - devCmd.Flags().MarkHidden("force-env") + devCmd.Flags().Bool("force", false, "Force the processing of environment files") } diff --git a/cmd/project.go b/cmd/project.go index 436bbf8c..12fd2a8e 100644 --- a/cmd/project.go +++ b/cmd/project.go @@ -821,7 +821,7 @@ Examples: } ShowNewProjectImport(ctx, logger, cmd, context.APIURL, context.Token, "", context.Project, context.Dir, true) - force, _ := cmd.Flags().GetBool("force-env") + force, _ := cmd.Flags().GetBool("force") if !tui.HasTTY { force = true } @@ -874,8 +874,7 @@ func init() { projectImportCmd.Flags().String("apikey", "", "The API key to use for the project") projectImportCmd.Flags().String("name", "", "The name of the project to import") projectImportCmd.Flags().String("description", "", "The description of the project to import") - projectImportCmd.Flags().Bool("force-env", false, "Force the processing of environment files") - projectImportCmd.Flags().MarkHidden("force-env") + projectImportCmd.Flags().Bool("force", false, "Force the processing of environment files") projectImportCmd.Flags().MarkHidden("apikey") projectImportCmd.Flags().MarkHidden("name") From 9fb81a0d58c727ea8a40e8727f52ba944c7fba1e Mon Sep 17 00:00:00 2001 From: Pedro Enrique Date: Wed, 21 May 2025 19:44:17 +0200 Subject: [PATCH 4/8] coderrabbit suggestions --- internal/envutil/envutil.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/envutil/envutil.go b/internal/envutil/envutil.go index 89e44471..23e9e3b0 100644 --- a/internal/envutil/envutil.go +++ b/internal/envutil/envutil.go @@ -52,6 +52,7 @@ func ProcessEnvFiles(ctx context.Context, logger logger.Logger, dir string, thep } projectData = HandleMissingProjectEnvs(ctx, logger, le, projectData, theproject, apiUrl, token, force) + envFile.Env = le return envFile, projectData } return envFile, projectData @@ -240,7 +241,7 @@ func AppendToEnvFile(envfile string, envs []env.EnvLineComment) ([]env.EnvLineCo buf.WriteString(fmt.Sprintf("%s=%s\n", ev.Key, ev.Raw)) le = append(le, ev) } - if err := os.WriteFile(envfile, []byte(buf.String()), 0644); err != nil { + if err := os.WriteFile(envfile, []byte(buf.String()), 0600); err != nil { return nil, err } return le, nil From f7f99aeac7bb91a1a3098be2b72acc239f305d44 Mon Sep 17 00:00:00 2001 From: Pedro Enrique Date: Wed, 21 May 2025 19:46:25 +0200 Subject: [PATCH 5/8] one last change --- internal/envutil/envutil.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/envutil/envutil.go b/internal/envutil/envutil.go index 23e9e3b0..1bfa4fd2 100644 --- a/internal/envutil/envutil.go +++ b/internal/envutil/envutil.go @@ -158,7 +158,7 @@ func HandleMissingProjectEnvs(ctx context.Context, logger logger.Logger, le []en case len(keyvalue) < 3 && len(keyvalue) > 1: suffix = "it" var colorized []string - for _, key := range keyvalue { + for key := range keyvalue { colorized = append(colorized, tui.Bold(key)) } title = fmt.Sprintf("The environment variables %s from %s are not been set in the project.", strings.Join(colorized, ", "), tui.Bold(".env")) From 7a9072620c381f70c6b4c59aa1685f30b8901ecf Mon Sep 17 00:00:00 2001 From: Pedro Enrique Date: Wed, 21 May 2025 19:52:42 +0200 Subject: [PATCH 6/8] more coderrabbit fixes --- internal/envutil/envutil.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/envutil/envutil.go b/internal/envutil/envutil.go index 1bfa4fd2..978f0788 100644 --- a/internal/envutil/envutil.go +++ b/internal/envutil/envutil.go @@ -106,9 +106,7 @@ func HandleMissingTemplateEnvs(logger logger.Logger, dir, envfilename string, le if ev.Val == "" { ev.Val = PromptForEnv(logger, ev.Key, isSecret, nil, osenv, ev.Val, ev.Comment) } - } - if ev.Val != "" { - continue + ev.Raw = ev.Val } addtoenvfile = append(addtoenvfile, env.EnvLineComment{ EnvLine: env.EnvLine{ @@ -238,7 +236,11 @@ func AppendToEnvFile(envfile string, envs []env.EnvLineComment) ([]env.EnvLineCo if ev.Comment != "" { buf.WriteString(fmt.Sprintf("# %s\n", ev.Comment)) } - buf.WriteString(fmt.Sprintf("%s=%s\n", ev.Key, ev.Raw)) + raw := ev.Raw + if raw == "" { + raw = ev.Val + } + buf.WriteString(fmt.Sprintf("%s=%s\n", ev.Key, raw)) le = append(le, ev) } if err := os.WriteFile(envfile, []byte(buf.String()), 0600); err != nil { From 504954768d6d4200b7d304ba70376dc3d1b769ac Mon Sep 17 00:00:00 2001 From: Pedro Enrique Date: Wed, 21 May 2025 20:06:18 +0200 Subject: [PATCH 7/8] More coderrabbit, more! --- internal/envutil/envutil.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/internal/envutil/envutil.go b/internal/envutil/envutil.go index 978f0788..e61aa330 100644 --- a/internal/envutil/envutil.go +++ b/internal/envutil/envutil.go @@ -135,15 +135,19 @@ func HandleMissingTemplateEnvs(logger logger.Logger, dir, envfilename string, le // HandleMissingProjectEnvs handles missing envs in project func HandleMissingProjectEnvs(ctx context.Context, logger logger.Logger, le []env.EnvLineComment, projectData *project.ProjectData, theproject *project.Project, apiUrl, token string, force bool) *project.ProjectData { + + if projectData == nil { + projectData = &project.ProjectData{} + } keyvalue := map[string]string{} for _, ev := range le { if isAgentuityEnv.MatchString(ev.Key) { continue } - if projectData != nil && projectData.Env != nil && projectData.Env[ev.Key] == ev.Val { + if projectData.Env != nil && projectData.Env[ev.Key] == ev.Val { continue } - if projectData != nil && projectData.Secrets != nil && projectData.Secrets[ev.Key] == cstr.Mask(ev.Val) { + if projectData.Secrets != nil && projectData.Secrets[ev.Key] == cstr.Mask(ev.Val) { continue } keyvalue[ev.Key] = ev.Val @@ -230,7 +234,11 @@ func AppendToEnvFile(envfile string, envs []env.EnvLineComment) ([]env.EnvLineCo if ev.Comment != "" { buf.WriteString(fmt.Sprintf("# %s\n", ev.Comment)) } - buf.WriteString(fmt.Sprintf("%s=%s\n", ev.Key, ev.Raw)) + raw := ev.Raw + if raw == "" { + raw = ev.Val + } + buf.WriteString(fmt.Sprintf("%s=%s\n", ev.Key, raw)) } for _, ev := range envs { if ev.Comment != "" { From 803a5ecf521eaffae76b316e7cc1ce4ae12ff74e Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Wed, 21 May 2025 13:54:58 -0500 Subject: [PATCH 8/8] nit cleanup --- internal/envutil/envutil.go | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/internal/envutil/envutil.go b/internal/envutil/envutil.go index e61aa330..065e81b6 100644 --- a/internal/envutil/envutil.go +++ b/internal/envutil/envutil.go @@ -166,8 +166,9 @@ func HandleMissingProjectEnvs(ctx context.Context, logger logger.Logger, le []en title = fmt.Sprintf("The environment variables %s from %s are not been set in the project.", strings.Join(colorized, ", "), tui.Bold(".env")) case len(keyvalue) == 1: var key string - for _key, _ := range keyvalue { + for _key := range keyvalue { key = _key + break } suffix = "it" title = fmt.Sprintf("The environment variable %s from %s has not been set in the project.", tui.Bold(key), tui.Bold(".env")) @@ -289,13 +290,6 @@ func LoadOSEnv() map[string]string { return osenv } -func maxString(val string, max int) string { - if len(val) > max { - return val[:max] + "..." - } - return val -} - func PromptForEnv(logger logger.Logger, key string, isSecret bool, localenv map[string]string, osenv map[string]string, defaultValue string, placeholder string) string { prompt := "Enter the value for " + key var help string @@ -303,12 +297,12 @@ func PromptForEnv(logger logger.Logger, key string, isSecret bool, localenv map[ if isSecret { prompt = "Enter the secret value for " + key if val, ok := localenv[key]; ok { - help = "Press enter to set as " + maxString(cstr.Mask(val), 30) + " from your .env file" + help = "Press enter to set as " + cstr.Mask(util.MaxString(val, 30)) + " from your .env file" if defaultValue == "" { defaultValue = val } } else if val, ok := osenv[key]; ok { - help = "Press enter to set as " + maxString(cstr.Mask(val), 30) + " from your environment" + help = "Press enter to set as " + cstr.Mask(util.MaxString(val, 30)) + " from your environment" if defaultValue == "" { defaultValue = val }