From 97b5cfdf397ecd32d015228e178825cbd476f2fe Mon Sep 17 00:00:00 2001 From: Jye Cusch Date: Sat, 12 Jul 2025 08:18:11 +1000 Subject: [PATCH 1/4] move prompt inputs to the ask package --- cli/pkg/app/nitric.go | 13 +++++++------ cli/pkg/tui/{ => ask}/errors.go | 2 +- cli/pkg/tui/{ => ask}/select.go | 2 +- cli/pkg/tui/{ => ask}/textinput.go | 2 +- cli/pkg/tui/{ => ask}/toggle.go | 2 +- 5 files changed, 11 insertions(+), 10 deletions(-) rename cli/pkg/tui/{ => ask}/errors.go (82%) rename cli/pkg/tui/{ => ask}/select.go (99%) rename cli/pkg/tui/{ => ask}/textinput.go (99%) rename cli/pkg/tui/{ => ask}/toggle.go (99%) diff --git a/cli/pkg/app/nitric.go b/cli/pkg/app/nitric.go index 010488b51..41c049363 100644 --- a/cli/pkg/app/nitric.go +++ b/cli/pkg/app/nitric.go @@ -28,6 +28,7 @@ import ( "github.com/nitrictech/nitric/cli/pkg/files" "github.com/nitrictech/nitric/cli/pkg/schema" "github.com/nitrictech/nitric/cli/pkg/tui" + "github.com/nitrictech/nitric/cli/pkg/tui/ask" "github.com/nitrictech/nitric/engines/terraform" "github.com/samber/do/v2" "github.com/spf13/afero" @@ -110,7 +111,7 @@ func (c *NitricApp) Init() error { fmt.Println() // Project Name Prompt - name, err := tui.RunTextInput("Project name:", func(input string) error { + name, err := ask.RunTextInput("Project name:", func(input string) error { if input == "" { return errors.New("project name is required") } @@ -127,7 +128,7 @@ func (c *NitricApp) Init() error { } // Project Description Prompt - description, err := tui.RunTextInput("Project description:", func(input string) error { + description, err := ask.RunTextInput("Project description:", func(input string) error { return nil }) if err != nil { @@ -178,7 +179,7 @@ func (c *NitricApp) New(projectName string, force bool) error { if projectName == "" { fmt.Println() var err error - projectName, err = tui.RunTextInput("Project name:", func(input string) error { + projectName, err = ask.RunTextInput("Project name:", func(input string) error { if input == "" { return errors.New("project name is required") } @@ -221,7 +222,7 @@ func (c *NitricApp) New(projectName string, force bool) error { // Prompt the user to select one of the templates fmt.Println("") - _, index, err := tui.RunSelect(templateNames, "Template:") + _, index, err := ask.RunSelect(templateNames, "Template:") if err != nil || index == -1 { return err } @@ -326,9 +327,9 @@ func (c *NitricApp) Build() error { if len(appSpec.Targets) == 1 { targetPlatform = appSpec.Targets[0] } else { - targetPlatform, _, err = tui.RunSelect(appSpec.Targets, "Select a build target") + targetPlatform, _, err = ask.RunSelect(appSpec.Targets, "Select a build target") if err != nil { - if errors.Is(err, tui.ErrUserAborted) { + if errors.Is(err, ask.ErrUserAborted) { return nil } return err diff --git a/cli/pkg/tui/errors.go b/cli/pkg/tui/ask/errors.go similarity index 82% rename from cli/pkg/tui/errors.go rename to cli/pkg/tui/ask/errors.go index 65fd1135e..a42a56a9d 100644 --- a/cli/pkg/tui/errors.go +++ b/cli/pkg/tui/ask/errors.go @@ -1,4 +1,4 @@ -package tui +package ask import "errors" diff --git a/cli/pkg/tui/select.go b/cli/pkg/tui/ask/select.go similarity index 99% rename from cli/pkg/tui/select.go rename to cli/pkg/tui/ask/select.go index 80ee84bb4..42795d612 100644 --- a/cli/pkg/tui/select.go +++ b/cli/pkg/tui/ask/select.go @@ -1,4 +1,4 @@ -package tui +package ask import ( "fmt" diff --git a/cli/pkg/tui/textinput.go b/cli/pkg/tui/ask/textinput.go similarity index 99% rename from cli/pkg/tui/textinput.go rename to cli/pkg/tui/ask/textinput.go index b00342804..2b71b5c4e 100644 --- a/cli/pkg/tui/textinput.go +++ b/cli/pkg/tui/ask/textinput.go @@ -1,4 +1,4 @@ -package tui +package ask import ( "strings" diff --git a/cli/pkg/tui/toggle.go b/cli/pkg/tui/ask/toggle.go similarity index 99% rename from cli/pkg/tui/toggle.go rename to cli/pkg/tui/ask/toggle.go index 32db7a2ac..e6faf8728 100644 --- a/cli/pkg/tui/toggle.go +++ b/cli/pkg/tui/ask/toggle.go @@ -1,4 +1,4 @@ -package tui +package ask import ( "fmt" From 1222f37e0eb476b66aa7ed2cfacda6b8b144ac65 Mon Sep 17 00:00:00 2001 From: Jye Cusch Date: Sat, 12 Jul 2025 18:43:45 +1000 Subject: [PATCH 2/4] migrate to huh tui prompt components --- cli/internal/style/colors/colors.go | 102 +++++++++++- cli/pkg/app/nitric.go | 190 ++++++++++++++--------- cli/pkg/tui/ask/input.go | 13 ++ cli/pkg/tui/ask/select.go | 232 +--------------------------- cli/pkg/tui/ask/textinput.go | 121 --------------- cli/pkg/tui/ask/toggle.go | 159 ------------------- 6 files changed, 237 insertions(+), 580 deletions(-) create mode 100644 cli/pkg/tui/ask/input.go delete mode 100644 cli/pkg/tui/ask/textinput.go delete mode 100644 cli/pkg/tui/ask/toggle.go diff --git a/cli/internal/style/colors/colors.go b/cli/internal/style/colors/colors.go index 0c4062741..5bb180dd1 100644 --- a/cli/internal/style/colors/colors.go +++ b/cli/internal/style/colors/colors.go @@ -1,6 +1,10 @@ package colors -import "github.com/charmbracelet/lipgloss" +import ( + "github.com/charmbracelet/huh" + "github.com/charmbracelet/lipgloss" + "github.com/nitrictech/nitric/cli/internal/style/icons" +) // @@ -48,3 +52,99 @@ var ( // Dark: AdaptiveBlue, // } // ) + +var Theme = huh.Theme{ + Form: huh.FormStyles{ + Base: lipgloss.NewStyle(), + }, + Group: huh.GroupStyles{ + Base: lipgloss.NewStyle(), + Title: lipgloss.NewStyle(), + Description: lipgloss.NewStyle(), + }, + FieldSeparator: lipgloss.NewStyle(), + Blurred: huh.FieldStyles{ + Base: lipgloss.NewStyle(), + Title: lipgloss.NewStyle().Faint(true), + Description: lipgloss.NewStyle().Faint(true), + ErrorIndicator: lipgloss.NewStyle(), + ErrorMessage: lipgloss.NewStyle(), + + // Select styles. + SelectSelector: lipgloss.NewStyle(), // Selection indicator + Option: lipgloss.NewStyle(), // Select options + NextIndicator: lipgloss.NewStyle(), + PrevIndicator: lipgloss.NewStyle(), + + // FilePicker styles. + Directory: lipgloss.NewStyle(), + File: lipgloss.NewStyle(), + + // Multi-select styles. + MultiSelectSelector: lipgloss.NewStyle(), + SelectedOption: lipgloss.NewStyle(), + SelectedPrefix: lipgloss.NewStyle(), + UnselectedOption: lipgloss.NewStyle(), + UnselectedPrefix: lipgloss.NewStyle(), + + // Textinput and teatarea styles. + TextInput: huh.TextInputStyles{ + Cursor: lipgloss.NewStyle(), + CursorText: lipgloss.NewStyle(), + Placeholder: lipgloss.NewStyle(), + Prompt: lipgloss.NewStyle(), + Text: lipgloss.NewStyle(), + }, + + // Confirm styles. + FocusedButton: lipgloss.NewStyle(), + BlurredButton: lipgloss.NewStyle(), + + // Card styles. + Card: lipgloss.NewStyle(), + NoteTitle: lipgloss.NewStyle(), + Next: lipgloss.NewStyle(), + }, + Focused: huh.FieldStyles{ + Base: lipgloss.NewStyle(), + Title: lipgloss.NewStyle().Foreground(Teal).Bold(true), + Description: lipgloss.NewStyle().Faint(true), + ErrorIndicator: lipgloss.NewStyle(), + ErrorMessage: lipgloss.NewStyle(), + + // Select styles. + SelectSelector: lipgloss.NewStyle().SetString(icons.RightArrow + " ").Foreground(Teal).Bold(true), // Selection indicator + Option: lipgloss.NewStyle(), // Select options + NextIndicator: lipgloss.NewStyle(), + PrevIndicator: lipgloss.NewStyle(), + + // FilePicker styles. + Directory: lipgloss.NewStyle(), + File: lipgloss.NewStyle(), + + // Multi-select styles. + MultiSelectSelector: lipgloss.NewStyle(), + SelectedOption: lipgloss.NewStyle().Foreground(Teal).Bold(true), + SelectedPrefix: lipgloss.NewStyle().SetString(icons.RightArrow).Foreground(Teal).Bold(true), + UnselectedOption: lipgloss.NewStyle(), + UnselectedPrefix: lipgloss.NewStyle(), + + // Textinput and teatarea styles. + TextInput: huh.TextInputStyles{ + Cursor: lipgloss.NewStyle(), + CursorText: lipgloss.NewStyle(), + Placeholder: lipgloss.NewStyle(), + Prompt: lipgloss.NewStyle(), + Text: lipgloss.NewStyle(), + }, + + // Confirm styles. + FocusedButton: lipgloss.NewStyle(), + BlurredButton: lipgloss.NewStyle(), + + // Card styles. + Card: lipgloss.NewStyle(), + NoteTitle: lipgloss.NewStyle(), + Next: lipgloss.NewStyle(), + }, +} diff --git a/cli/pkg/app/nitric.go b/cli/pkg/app/nitric.go index 41c049363..3110613e2 100644 --- a/cli/pkg/app/nitric.go +++ b/cli/pkg/app/nitric.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/charmbracelet/huh" "github.com/charmbracelet/lipgloss" "github.com/hashicorp/go-getter" "github.com/nitrictech/nitric/cli/internal/api" @@ -111,30 +112,40 @@ func (c *NitricApp) Init() error { fmt.Println() // Project Name Prompt - name, err := ask.RunTextInput("Project name:", func(input string) error { - if input == "" { - return errors.New("project name is required") - } - - // Must be kebab-case - if !regexp.MustCompile(`^[a-z][a-z0-9-]*$`).MatchString(input) { - return errors.New("project name must start with a letter and be lower kebab-case") - } - - return nil - }) - if err != nil || name == "" { + var name string + err = ask.NewInput(). + Title("Project name:"). + Value(&name). + Validate(validateProjName). + Run() + + if errors.Is(err, huh.ErrUserAborted) { return nil } + if err != nil { + return err + } + + fmt.Printf("Project name: %s\n", name) + // Project Description Prompt - description, err := ask.RunTextInput("Project description:", func(input string) error { + var description string + err = ask.NewInput(). + Title("Project description:"). + Value(&description). + Run() + + if errors.Is(err, huh.ErrUserAborted) { return nil - }) + } + if err != nil { - return nil + return err } + fmt.Printf("Project description: %s\n", description) + newProject := &schema.Application{ Name: name, Description: description, @@ -163,6 +174,19 @@ func (c *NitricApp) Init() error { return nil } +func validateProjName(name string) error { + if name == "" { + return errors.New("project name is required") + } + + // Must be kebab-case + if !regexp.MustCompile(`^[a-z][a-z0-9-]*$`).MatchString(name) { + return errors.New("project name must start with a letter and be lower kebab-case") + } + + return nil +} + // New handles the new project creation command logic func (c *NitricApp) New(projectName string, force bool) error { templates, err := c.apiClient.GetTemplates() @@ -172,108 +196,118 @@ func (c *NitricApp) New(projectName string, force bool) error { return nil } - fmt.Printf("Failed to get templates: %v\n", err) - return nil + return fmt.Errorf("failed to get templates: %v", err) } - if projectName == "" { - fmt.Println() - var err error - projectName, err = ask.RunTextInput("Project name:", func(input string) error { - if input == "" { - return errors.New("project name is required") - } + fmt.Printf("Welcome to %s, this command will help you create a project from a template.\n", c.styles.emphasize.Render("Nitric")) + fmt.Printf("If you already have a project, run %s instead.\n", c.styles.emphasize.Render("nitric init")) + fmt.Println() - // Must be kebab-case - if !regexp.MustCompile(`^[a-z][a-z0-9-]*$`).MatchString(input) { - return errors.New("project name must start with a letter and be lower kebab-case") - } + if projectName == "" { + err := ask.NewInput(). + Title("Project name:"). + Value(&projectName). + Validate(validateProjName). + Run() - return nil - }) - if err != nil || projectName == "" { - fmt.Println(err) + if errors.Is(err, huh.ErrUserAborted) { return nil } + + if err != nil { + return err + } } + fmt.Printf("Project name: %s\n", projectName) + projectDir := filepath.Join(".", projectName) if !force { projectExists, err := projectExists(c.fs, projectDir) if err != nil { - fmt.Println(err.Error()) - return nil + return fmt.Errorf("failed to check if project directory exists: %v", err) } if projectExists { - fmt.Printf("\nDirectory ./%s already exists and is not empty\n", projectDir) - return errors.New("project directory already exists") + return fmt.Errorf("project directory %s already exists, use --force to overwrite", projectDir) } } if len(templates) == 0 { - fmt.Println("No templates found") - return errors.New("no templates available") + return errors.New("no templates found") } - templateNames := make([]string, len(templates)) + templateNames := make([]huh.Option[*api.Template], len(templates)) for i, template := range templates { - templateNames[i] = template.String() + templateNames[i] = huh.NewOption(template.String(), &template) } // Prompt the user to select one of the templates - fmt.Println("") - _, index, err := ask.RunSelect(templateNames, "Template:") - if err != nil || index == -1 { - return err + var template *api.Template + err = ask.NewSelect[*api.Template](). + Title("Template:"). + Validate(func(template *api.Template) error { + if template == nil { + return errors.New("template is required") + } + + return nil + }). + Options(templateNames...). + Value(&template). + Run() + + if errors.Is(err, huh.ErrUserAborted) { + return nil } - template, err := c.apiClient.GetTemplate(templates[index].TeamSlug, templates[index].Slug, "") if err != nil { return err } + fmt.Printf("Template: %s\n", template.String()) + + latestVersion, err := c.apiClient.GetTemplate(template.TeamSlug, template.Slug, "") + if err != nil { + return fmt.Errorf("failed to get template: %v", err) + } + // Find home directory. home, err := os.UserHomeDir() if err != nil { - fmt.Println(err) - return err + return fmt.Errorf("failed to get home directory: %v", err) } - templateDir := filepath.Join(home, ".nitric", "templates", template.TeamSlug, template.TemplateSlug, template.Version) + templateDir := filepath.Join(home, ".nitric", "templates", latestVersion.TeamSlug, latestVersion.TemplateSlug, latestVersion.Version) templateCached, err := afero.Exists(c.fs, filepath.Join(templateDir, "nitric.yaml")) if err != nil { - fmt.Printf("Failed read template cache directory: %v\n", err) - return err + return fmt.Errorf("failed read template cache directory: %v", err) } if !templateCached { goGetter := &getter.Client{ Ctx: context.Background(), Dst: templateDir, - Src: template.GitSource, + Src: latestVersion.GitSource, Mode: getter.ClientModeAny, DisableSymlinks: true, } err = goGetter.Get() if err != nil { - fmt.Printf("Failed to get template: %v\n", err) - return err + return fmt.Errorf("failed to get template: %v", err) } } // Copy the template dir contents into a new project dir err = os.MkdirAll(projectDir, 0755) if err != nil { - fmt.Printf("Failed to create project directory: %v\n", err) - return err + return fmt.Errorf("failed to create project directory: %v", err) } err = files.CopyDir(c.fs, templateDir, projectDir) if err != nil { - fmt.Printf("Failed to copy template directory: %v\n", err) - return err + return fmt.Errorf("failed to copy template directory: %v", err) } nitricYamlPath := filepath.Join(projectDir, "nitric.yaml") @@ -290,20 +324,21 @@ func (c *NitricApp) New(projectName string, force bool) error { return err } - successStyle := lipgloss.NewStyle().MarginLeft(3) - highlight := lipgloss.NewStyle().Foreground(colors.Teal).Bold(true) - var b strings.Builder b.WriteString("\n") - b.WriteString("Project created!") - b.WriteString("\n\n") - b.WriteString("Navigate to your project with ") - b.WriteString(highlight.Render("cd ./" + projectDir)) + b.WriteString(c.styles.emphasize.Render(icons.Check + " Project created!\n")) b.WriteString("\n") - b.WriteString("Install dependencies and you're ready to rock! 🪨") + b.WriteString("Next steps:\n") + b.WriteString("1. Run " + c.styles.emphasize.Render("cd ./"+projectDir) + " to move to the project directory\n") + b.WriteString("2. Run " + c.styles.emphasize.Render("nitric edit") + " to start the nitric editor\n") + b.WriteString("3. Design your app's resources and deployment targets\n") + b.WriteString("4. Run " + c.styles.emphasize.Render("nitric dev") + " to start the development server\n") + b.WriteString("5. Run " + c.styles.emphasize.Render("nitric build") + " to build the project for a specific platform\n") + b.WriteString("\n") + b.WriteString("For more information, see the " + c.styles.emphasize.Render("nitric docs") + " at " + c.styles.emphasize.Render("https://nitric.io/docs")) - fmt.Println(successStyle.Render(b.String())) + fmt.Println(b.String()) return nil } @@ -327,11 +362,24 @@ func (c *NitricApp) Build() error { if len(appSpec.Targets) == 1 { targetPlatform = appSpec.Targets[0] } else { - targetPlatform, _, err = ask.RunSelect(appSpec.Targets, "Select a build target") - if err != nil { - if errors.Is(err, ask.ErrUserAborted) { + err := ask.NewSelect[string](). + Title("Select a build target"). + Options(huh.NewOptions(appSpec.Targets...)...). + Value(&targetPlatform). + Validate(func(targetPlatform string) error { + if targetPlatform == "" { + return errors.New("target platform is required") + } + return nil - } + }). + Run() + + if errors.Is(err, huh.ErrUserAborted) { + return nil + } + + if err != nil { return err } } diff --git a/cli/pkg/tui/ask/input.go b/cli/pkg/tui/ask/input.go new file mode 100644 index 000000000..d227f085c --- /dev/null +++ b/cli/pkg/tui/ask/input.go @@ -0,0 +1,13 @@ +package ask + +import ( + "github.com/charmbracelet/huh" + "github.com/nitrictech/nitric/cli/internal/style/colors" +) + +func NewInput() *huh.Input { + return huh.NewInput(). + Inline(true). + Prompt(" "). + WithTheme(&colors.Theme).(*huh.Input) +} diff --git a/cli/pkg/tui/ask/select.go b/cli/pkg/tui/ask/select.go index 42795d612..adf3e78b7 100644 --- a/cli/pkg/tui/ask/select.go +++ b/cli/pkg/tui/ask/select.go @@ -1,235 +1,11 @@ package ask import ( - "fmt" - "strings" - - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/huh" "github.com/nitrictech/nitric/cli/internal/style/colors" - "github.com/nitrictech/nitric/cli/internal/style/icons" ) -// Select represents a select input component -type Select struct { - items []string - cursor int - selected int - title string - width int - height int - showHelp bool - helpText string - style SelectStyle - maxDisplayed int - viewCursor int - quit bool -} - -const TitleAndHelpHeight = 3 - -func (s *Select) numToDisplay() int { - return max(min(min(s.maxDisplayed, len(s.items)), s.height-TitleAndHelpHeight), 1) -} - -// SelectStyle defines the styling for the select component -type SelectStyle struct { - Title lipgloss.Style - Item lipgloss.Style - Selected lipgloss.Style - Faint lipgloss.Style - Cursor lipgloss.Style - Help lipgloss.Style -} - -// NewSelect creates a new select component -func NewSelect(items []string, title string, maxDisplayed int) *Select { - return &Select{ - items: items, - title: title, - selected: -1, - showHelp: true, - helpText: "↑/↓: navigate • enter: select", - maxDisplayed: maxDisplayed, - viewCursor: 0, - style: SelectStyle{ - Title: lipgloss.NewStyle(). - Foreground(colors.Teal). - Bold(true), - Item: lipgloss.NewStyle(). - Foreground(colors.White). - MarginLeft(1), - Selected: lipgloss.NewStyle(). - Foreground(colors.Teal). - Bold(true), - // MarginLeft(1), - Cursor: lipgloss.NewStyle(). - Foreground(colors.Teal). - Bold(true). - MarginRight(1), - Faint: lipgloss.NewStyle(). - Faint(true), - Help: lipgloss.NewStyle(). - Foreground(colors.Gray). - Italic(true), - }, - } -} - -// Init initializes the select component -func (s *Select) Init() tea.Cmd { - return nil -} - -// Update handles user input and updates the component state -func (s *Select) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.String() { - case "up", "k": - if s.cursor > 0 { - s.cursor-- - if s.cursor < s.viewCursor { - s.viewCursor-- - } - } - case "down", "j": - if s.cursor < len(s.items)-1 { - s.cursor++ - if s.cursor >= s.viewCursor+s.numToDisplay() { - s.viewCursor++ - } - } - case "enter", " ": - s.selected = s.cursor - return s, tea.Quit - case "q", "ctrl+c", "ctrl+\\", "esc": - s.quit = true - return s, tea.Quit - } - case tea.WindowSizeMsg: - s.width = msg.Width - s.height = msg.Height - // Center the view, but don't go below 0 - s.viewCursor = max(0, s.cursor-(s.numToDisplay()/2)) - return s, nil - } - - return s, nil -} - -func (s *Select) isInView(itemIndex int) bool { - return itemIndex >= s.viewCursor && itemIndex < s.viewCursor+s.numToDisplay() -} - -// View renders the select component -func (s *Select) View() string { - if len(s.items) == 0 { - return "No items available" - } - - var b strings.Builder - - if s.selected != -1 { - return s.style.Faint.Render(s.title) + " " + s.items[s.selected] + "\n" - } - - // Title - if s.title != "" { - b.WriteString(s.style.Title.Render(s.title)) - b.WriteString("\n") - } - - // Help text - if s.showHelp && s.helpText != "" && s.height >= TitleAndHelpHeight { - b.WriteString(s.style.Help.Render(s.helpText)) - b.WriteString("\n") - if s.height >= TitleAndHelpHeight+1 { - b.WriteString("\n") - } - } - - // Items - shownItems := make([]string, 0, s.numToDisplay()) - for i, item := range s.items { - if !s.isInView(i) { - continue - } - - cursor := " " - if s.cursor == i { - cursor = s.style.Cursor.Render(icons.RightArrow) - } - - itemStyle := s.style.Item - if s.cursor == i { - itemStyle = s.style.Selected - } - - shownItems = append(shownItems, fmt.Sprintf("%s%s", cursor, itemStyle.Render(item))) - } - - b.WriteString(strings.Join(shownItems, "\n")) - - if s.selected == -1 && s.height >= s.numToDisplay()+TitleAndHelpHeight+1 { - b.WriteString("\n") - } - - return b.String() -} - -// GetSelected returns the selected item and its index -func (s *Select) GetSelected() (string, int) { - if s.selected >= 0 && s.selected < len(s.items) { - return s.items[s.selected], s.selected - } - return "", -1 -} - -// SetItems updates the items in the select component -func (s *Select) SetItems(items []string) { - s.items = items - if s.cursor >= len(items) { - s.cursor = len(items) - 1 - } - if s.cursor < 0 && len(items) > 0 { - s.cursor = 0 - } -} - -// SetTitle updates the title of the select component -func (s *Select) SetTitle(title string) { - s.title = title -} - -// SetHelpText updates the help text -func (s *Select) SetHelpText(helpText string) { - s.helpText = helpText -} - -// SetShowHelp toggles the visibility of help text -func (s *Select) SetShowHelp(show bool) { - s.showHelp = show -} - -// RunSelect runs the select component and returns the selected item and index -func RunSelect(items []string, title string) (string, int, error) { - if len(items) == 0 { - return "", -1, fmt.Errorf("no items provided") - } - - s := NewSelect(items, title, 5) - p := tea.NewProgram(s) - - _, err := p.Run() - if err != nil { - return "", -1, err - } - - if s.quit { - return "", -1, ErrUserAborted - } - - selected, index := s.GetSelected() - return selected, index, nil +func NewSelect[T comparable]() *huh.Select[T] { + return huh.NewSelect[T](). + WithTheme(&colors.Theme).(*huh.Select[T]) } diff --git a/cli/pkg/tui/ask/textinput.go b/cli/pkg/tui/ask/textinput.go deleted file mode 100644 index 2b71b5c4e..000000000 --- a/cli/pkg/tui/ask/textinput.go +++ /dev/null @@ -1,121 +0,0 @@ -package ask - -import ( - "strings" - - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/nitrictech/nitric/cli/internal/style/colors" -) - -type textInputModel struct { - textinput textinput.Model - title string - style textInputStyle - showError bool - - quit bool - - value string -} - -type textInputStyle struct { - Title lipgloss.Style - Input lipgloss.Style - Faint lipgloss.Style - Invalid lipgloss.Style - Help lipgloss.Style -} - -func (m textInputModel) Init() tea.Cmd { - m.textinput.Focus() - return textinput.Blink -} - -func (m textInputModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmd tea.Cmd - - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.String() { - case "ctrl+c", "esc", "ctrl+\\": - m.quit = true - return m, tea.Quit - case "enter": - m.textinput, _ = m.textinput.Update(msg) - if m.textinput.Err != nil { - m.showError = true - return m, nil - } - m.value = m.textinput.Value() - m.textinput.PromptStyle = m.style.Faint.MarginRight(1) - m.textinput.Blur() - return m, tea.Quit - } - } - - // Always pass the message to the internal textinput component - m.textinput, cmd = m.textinput.Update(msg) - return m, cmd -} - -func (m textInputModel) View() string { - var b strings.Builder - - m.textinput.TextStyle = m.style.Input - if m.showError && m.textinput.Err != nil { - m.textinput.TextStyle = m.style.Invalid - } - - b.WriteString(m.textinput.View()) - b.WriteString("\n") - - if m.textinput.Err != nil && m.showError { - b.WriteString(m.style.Help.Render(m.textinput.Err.Error())) - b.WriteString("\n") - } - - if m.textinput.Value() == "" { - b.WriteString("\n") - } - - return b.String() -} - -func RunTextInput(title string, validate func(string) error) (string, error) { - ti := textinput.New() - ti.Placeholder = "" - ti.CharLimit = 200 - ti.Width = 50 - ti.Prompt = title - ti.PromptStyle = lipgloss.NewStyle().Foreground(colors.Teal).Bold(true).MarginRight(1) - ti.Validate = validate - ti.Focus() - - style := textInputStyle{ - Title: lipgloss.NewStyle().Foreground(colors.Teal).Bold(true), - Input: lipgloss.NewStyle().Foreground(colors.White), - Faint: lipgloss.NewStyle().Faint(true), - Invalid: lipgloss.NewStyle().Foreground(colors.Red), - Help: lipgloss.NewStyle().Faint(true).Italic(true), - } - - model := textInputModel{ - textinput: ti, - title: title, - style: style, - } - - p := tea.NewProgram(model) - m, err := p.Run() - if err != nil { - return "", err - } - - if m.(textInputModel).quit { - return "", ErrUserAborted - } - - return m.(textInputModel).value, nil -} diff --git a/cli/pkg/tui/ask/toggle.go b/cli/pkg/tui/ask/toggle.go deleted file mode 100644 index e6faf8728..000000000 --- a/cli/pkg/tui/ask/toggle.go +++ /dev/null @@ -1,159 +0,0 @@ -package ask - -import ( - "fmt" - "strings" - - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/nitrictech/nitric/cli/internal/style/colors" -) - -// ToggleSelect represents a toggle select input component, e.g. yes/no or yes/no/maybe -type ToggleSelect struct { - items []string - cursor int - selected int - title string - style ToggleSelectStyle - quit bool -} - -// ToggleSelectStyle defines the styling for the select component -type ToggleSelectStyle struct { - Title lipgloss.Style - Item lipgloss.Style - Selected lipgloss.Style - Faint lipgloss.Style -} - -// NewToggleSelect creates a new toggle select component -func NewToggleSelect(items []string, title string) *ToggleSelect { - return &ToggleSelect{ - items: items, - title: title, - selected: -1, - style: ToggleSelectStyle{ - Title: lipgloss.NewStyle(). - Foreground(colors.Teal). - Bold(true), - Item: lipgloss.NewStyle(). - Foreground(colors.White), - Selected: lipgloss.NewStyle(). - Foreground(colors.Teal). - Bold(true), - Faint: lipgloss.NewStyle(). - Faint(true), - }, - } -} - -// Init initializes the select component -func (s *ToggleSelect) Init() tea.Cmd { - return nil -} - -// Update handles user input and updates the component state -func (s *ToggleSelect) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.String() { - case "up", "left", "k": - if s.cursor > 0 { - s.cursor-- - } - case "down", "right", "j": - if s.cursor < len(s.items)-1 { - s.cursor++ - } - case "enter", " ": - s.selected = s.cursor - return s, tea.Quit - case "q", "ctrl+c", "ctrl+\\", "esc": - s.quit = true - return s, tea.Quit - } - } - - return s, nil -} - -// View renders the select component -func (s *ToggleSelect) View() string { - if len(s.items) == 0 { - return "No items available" - } - - var b strings.Builder - - if s.selected != -1 { - return s.style.Faint.Render(s.title) + " " + s.items[s.selected] + "\n" - } - - // Title - if s.title != "" { - b.WriteString(s.style.Title.Render(s.title)) - b.WriteString(" ") - } - - // Items - shownItems := make([]string, 0, len(s.items)) - for i, item := range s.items { - itemStyle := s.style.Item - if s.cursor == i { - itemStyle = s.style.Selected - } - - shownItems = append(shownItems, itemStyle.Render(item)) - } - - b.WriteString(strings.Join(shownItems, "/")) - - return b.String() -} - -// GetSelected returns the selected item and its index -func (s *ToggleSelect) GetSelected() (string, int) { - if s.selected >= 0 && s.selected < len(s.items) { - return s.items[s.selected], s.selected - } - return "", -1 -} - -// SetItems updates the items in the select component -func (s *ToggleSelect) SetItems(items []string) { - s.items = items - if s.cursor >= len(items) { - s.cursor = len(items) - 1 - } - if s.cursor < 0 && len(items) > 0 { - s.cursor = 0 - } -} - -// SetTitle updates the title of the select component -func (s *ToggleSelect) SetTitle(title string) { - s.title = title -} - -// RunToggleSelect runs the select component and returns the selected item and index -func RunToggleSelect(items []string, title string) (string, int, error) { - if len(items) == 0 { - return "", -1, fmt.Errorf("no items provided") - } - - s := NewToggleSelect(items, title) - p := tea.NewProgram(s) - - _, err := p.Run() - if err != nil { - return "", -1, err - } - - if s.quit { - return "", -1, ErrUserAborted - } - - selected, index := s.GetSelected() - return selected, index, nil -} From f2182efb2520c120426479fba67df2c9b1b92a9f Mon Sep 17 00:00:00 2001 From: Tim Holm Date: Mon, 14 Jul 2025 08:18:35 +1000 Subject: [PATCH 3/4] make command outputs more consistent and add constant for product name --- cli/cmd/auth.go | 11 +++++++---- cli/cmd/nitric.go | 25 +++++++++++++----------- cli/cmd/root.go | 12 +++++------- cli/internal/version/version.go | 11 +++++++++++ cli/pkg/app/nitric.go | 34 ++++++++++++++++----------------- cli/pkg/tui/intro.go | 2 +- 6 files changed, 55 insertions(+), 40 deletions(-) diff --git a/cli/cmd/auth.go b/cli/cmd/auth.go index ece7f1a8f..2ec35a91a 100644 --- a/cli/cmd/auth.go +++ b/cli/cmd/auth.go @@ -1,6 +1,9 @@ package cmd import ( + "fmt" + + "github.com/nitrictech/nitric/cli/internal/version" "github.com/nitrictech/nitric/cli/pkg/app" "github.com/samber/do/v2" "github.com/spf13/cobra" @@ -10,8 +13,8 @@ import ( func NewLoginCmd(injector do.Injector) *cobra.Command { return &cobra.Command{ Use: "login", - Short: "Login to Nitric", - Long: `Login to the Nitric CLI.`, + Short: fmt.Sprintf("Login to %s", version.ProductName), + Long: fmt.Sprintf("Login to the %s CLI.", version.ProductName), Run: func(cmd *cobra.Command, args []string) { app, err := do.Invoke[*app.AuthApp](injector) if err != nil { @@ -26,8 +29,8 @@ func NewLoginCmd(injector do.Injector) *cobra.Command { func NewLogoutCmd(injector do.Injector) *cobra.Command { return &cobra.Command{ Use: "logout", - Short: "Logout from Nitric", - Long: `Logout from the Nitric CLI.`, + Short: fmt.Sprintf("Logout from %s", version.ProductName), + Long: fmt.Sprintf("Logout from the %s CLI.", version.ProductName), Run: func(cmd *cobra.Command, args []string) { app, err := do.Invoke[*app.AuthApp](injector) if err != nil { diff --git a/cli/cmd/nitric.go b/cli/cmd/nitric.go index ce949cbf3..e0210d07b 100644 --- a/cli/cmd/nitric.go +++ b/cli/cmd/nitric.go @@ -1,6 +1,9 @@ package cmd import ( + "fmt" + + "github.com/nitrictech/nitric/cli/internal/version" "github.com/nitrictech/nitric/cli/pkg/app" "github.com/samber/do/v2" "github.com/spf13/cobra" @@ -26,8 +29,8 @@ func NewTemplatesCmd(injector do.Injector) *cobra.Command { func NewInitCmd(injector do.Injector) *cobra.Command { return &cobra.Command{ Use: "init", - Short: "Setup a new Nitric project", - Long: `Setup a new Nitric project, including within existing applications`, + Short: fmt.Sprintf("Setup a new %s project", version.ProductName), + Long: fmt.Sprintf("Setup a new %s project, including within existing applications", version.ProductName), Run: func(cmd *cobra.Command, args []string) { app, err := do.Invoke[*app.NitricApp](injector) if err != nil { @@ -44,8 +47,8 @@ func NewNewCmd(injector do.Injector) *cobra.Command { cmd := &cobra.Command{ Use: "new [project-name]", - Short: "Create a new Nitric project", - Long: `Create a new Nitric project from a template.`, + Short: fmt.Sprintf("Create a new %s project", version.ProductName), + Long: fmt.Sprintf("Create a new %s project from a template.", version.ProductName), Run: func(cmd *cobra.Command, args []string) { projectName := "" if len(args) > 0 { @@ -67,8 +70,8 @@ func NewNewCmd(injector do.Injector) *cobra.Command { func NewBuildCmd(injector do.Injector) *cobra.Command { return &cobra.Command{ Use: "build", - Short: "Builds the nitric application", - Long: `Builds an application using the nitric.yaml application spec and referenced platform.`, + Short: fmt.Sprintf("Builds the %s application", version.ProductName), + Long: "Builds an application using the nitric.yaml application spec and referenced platform.", Run: func(cmd *cobra.Command, args []string) { app, err := do.Invoke[*app.NitricApp](injector) if err != nil { @@ -88,8 +91,8 @@ func NewGenerateCmd(injector do.Injector) *cobra.Command { cmd := &cobra.Command{ Use: "generate", - Short: "Generate client libraries", - Long: `Generate client libraries for different programming languages based on the Nitric application specification.`, + Short: fmt.Sprintf("Generate client libraries for %s", version.ProductName), + Long: fmt.Sprintf("Generate client libraries for different programming languages based on the %s application specification.", version.ProductName), Run: func(cmd *cobra.Command, args []string) { app, err := do.Invoke[*app.NitricApp](injector) if err != nil { @@ -120,7 +123,7 @@ func NewGenerateCmd(injector do.Injector) *cobra.Command { func NewEditCmd(injector do.Injector) *cobra.Command { return &cobra.Command{ Use: "edit", - Short: "Edit the nitric application", + Short: fmt.Sprintf("Edit the %s application", version.ProductName), Long: `Edits an application using the nitric.yaml application spec and referenced platform.`, Run: func(cmd *cobra.Command, args []string) { app, err := do.Invoke[*app.NitricApp](injector) @@ -136,8 +139,8 @@ func NewEditCmd(injector do.Injector) *cobra.Command { func NewDevCmd(injector do.Injector) *cobra.Command { return &cobra.Command{ Use: "dev", - Short: "Run the Nitric application in development mode", - Long: `Run the Nitric application in development mode, allowing local testing of resources.`, + Short: fmt.Sprintf("Run the %s application in development mode", version.ProductName), + Long: fmt.Sprintf("Run the %s application in development mode, allowing local testing of resources.", version.ProductName), Run: func(cmd *cobra.Command, args []string) { cobra.CheckErr(app.Dev()) }, diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 8148458f8..8bd2c0f57 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -13,10 +13,8 @@ import ( func NewRootCmd(injector do.Injector) *cobra.Command { rootCmd := &cobra.Command{ Use: "nitric", - Short: "Nitric CLI - The command line interface for Nitric", - Long: `Nitric CLI is a command line interface for managing and deploying -Nitric applications. It provides a set of commands to help you develop, -test, and deploy your Nitric applications.`, + Short: fmt.Sprintf("%s CLI - The command line interface for %s", version.ProductName, version.ProductName), + Long: fmt.Sprintf("%s CLI is a command line interface for managing and deploying %s applications. It provides a set of commands to help you develop, test, and deploy your %s applications.", version.ProductName, version.ProductName, version.ProductName), PersistentPreRunE: func(cmd *cobra.Command, args []string) error { conf, err := config.Load(cmd) if err != nil { @@ -49,11 +47,11 @@ test, and deploy your Nitric applications.`, func NewVersionCmd(injector do.Injector) *cobra.Command { return &cobra.Command{ Use: "version", - Short: "Print the CLI version", - Long: `Display the version number of the Nitric CLI.`, + Short: fmt.Sprintf("Print the %s CLI version", version.ProductName), + Long: fmt.Sprintf("Display the version number of the %s CLI.", version.ProductName), Run: func(cmd *cobra.Command, args []string) { highlight := lipgloss.NewStyle().Foreground(lipgloss.Color("14")) - fmt.Printf("nitric cli version %s\n", highlight.Render(version.Version)) + fmt.Printf("%s cli version %s\n", version.ProductName, highlight.Render(version.Version)) }, } } diff --git a/cli/internal/version/version.go b/cli/internal/version/version.go index 73494715c..cc9afd9b8 100644 --- a/cli/internal/version/version.go +++ b/cli/internal/version/version.go @@ -2,10 +2,17 @@ package version import ( "fmt" + "os" "strings" ) var ( + // Name of the product + ProductName = "Nitric" + + // The name of the command that this process was executed as + CommandName = os.Args[0] + // Raw is the string representation of the version. This will be replaced // with the calculated version at build time. // set in the Makefile. @@ -20,6 +27,10 @@ var ( BuildTime = "unknown" ) +func GetCommand(suffix string) string { + return fmt.Sprintf("%s %s", CommandName, suffix) +} + func GetShortVersion() string { if strings.HasSuffix(Version, "dirty") { return fmt.Sprintf("Pre-release (%s)", Commit) diff --git a/cli/pkg/app/nitric.go b/cli/pkg/app/nitric.go index 3110613e2..3c1fa81c3 100644 --- a/cli/pkg/app/nitric.go +++ b/cli/pkg/app/nitric.go @@ -22,9 +22,9 @@ import ( "github.com/nitrictech/nitric/cli/internal/platforms" "github.com/nitrictech/nitric/cli/internal/plugins" "github.com/nitrictech/nitric/cli/internal/simulation" - "github.com/nitrictech/nitric/cli/internal/style" "github.com/nitrictech/nitric/cli/internal/style/colors" "github.com/nitrictech/nitric/cli/internal/style/icons" + "github.com/nitrictech/nitric/cli/internal/version" "github.com/nitrictech/nitric/cli/pkg/client" "github.com/nitrictech/nitric/cli/pkg/files" "github.com/nitrictech/nitric/cli/pkg/schema" @@ -98,17 +98,17 @@ func (c *NitricApp) Init() error { // Read the nitric.yaml file _, err := schema.LoadFromFile(c.fs, nitricYamlPath, true) if err == nil { - fmt.Printf("Project already initialized, run %s to edit the project\n", c.styles.emphasize.Render("nitric edit")) + fmt.Printf("Project already initialized, run %s to edit the project\n", c.styles.emphasize.Render(version.GetCommand("edit"))) return nil } else if exists { fmt.Printf("Project already initialized, but an error occurred loading %s\n", c.styles.emphasize.Render("nitric.yaml")) return err } - fmt.Printf("Welcome to %s, this command will walk you through creating a nitric.yaml file.\n", c.styles.emphasize.Render("Nitric")) + fmt.Printf("Welcome to %s, this command will walk you through creating a nitric.yaml file.\n", c.styles.emphasize.Render(version.ProductName)) fmt.Printf("This file is used to define your app's infrastructure, resources and deployment targets.\n") fmt.Println() - fmt.Printf("Here we'll only cover the basics, use %s to continue editing the project.\n", c.styles.emphasize.Render("nitric edit")) + fmt.Printf("Here we'll only cover the basics, use %s to continue editing the project.\n", c.styles.emphasize.Render(version.GetCommand("edit"))) fmt.Println() // Project Name Prompt @@ -163,13 +163,13 @@ func (c *NitricApp) Init() error { fmt.Println() fmt.Println("Next steps:") - fmt.Println("1. Run", c.styles.emphasize.Render("nitric edit"), "to start the nitric editor") + fmt.Println("1. Run", c.styles.emphasize.Render(version.GetCommand("edit")), "to start the nitric editor") fmt.Println("2. Design your app's resources and deployment targets") - fmt.Println("3. Optionally, use", c.styles.emphasize.Render("nitric generate"), "to generate the client libraries for your app") - fmt.Println("4. Run", c.styles.emphasize.Render("nitric dev"), "to start the development server") - fmt.Println("5. Run", c.styles.emphasize.Render("nitric build"), "to build the project for a specific platform") + fmt.Println("3. Optionally, use", c.styles.emphasize.Render(version.GetCommand("generate")), "to generate the client libraries for your app") + fmt.Println("4. Run", c.styles.emphasize.Render(version.GetCommand("dev")), "to start the development server") + fmt.Println("5. Run", c.styles.emphasize.Render(version.GetCommand("build")), "to build the project for a specific platform") fmt.Println() - fmt.Println("For more information, see the", c.styles.emphasize.Render("nitric docs"), "at", c.styles.emphasize.Render("https://nitric.io/docs")) + fmt.Println("For more information, see the", c.styles.emphasize.Render(version.ProductName+" docs"), "at", c.styles.emphasize.Render("https://nitric.io/docs")) return nil } @@ -192,15 +192,15 @@ func (c *NitricApp) New(projectName string, force bool) error { templates, err := c.apiClient.GetTemplates() if err != nil { if errors.Is(err, api.ErrUnauthenticated) { - fmt.Println("Please login first, using the `login` command") + fmt.Println("Please login first, using", c.styles.emphasize.Render(version.GetCommand("login"))) return nil } return fmt.Errorf("failed to get templates: %v", err) } - fmt.Printf("Welcome to %s, this command will help you create a project from a template.\n", c.styles.emphasize.Render("Nitric")) - fmt.Printf("If you already have a project, run %s instead.\n", c.styles.emphasize.Render("nitric init")) + fmt.Printf("Welcome to %s, this command will help you create a project from a template.\n", c.styles.emphasize.Render(version.ProductName)) + fmt.Printf("If you already have a project, run %s instead.\n", c.styles.emphasize.Render(version.GetCommand("init"))) fmt.Println() if projectName == "" { @@ -331,10 +331,10 @@ func (c *NitricApp) New(projectName string, force bool) error { b.WriteString("\n") b.WriteString("Next steps:\n") b.WriteString("1. Run " + c.styles.emphasize.Render("cd ./"+projectDir) + " to move to the project directory\n") - b.WriteString("2. Run " + c.styles.emphasize.Render("nitric edit") + " to start the nitric editor\n") + b.WriteString("2. Run " + c.styles.emphasize.Render(version.GetCommand("edit")) + " to start the nitric editor\n") b.WriteString("3. Design your app's resources and deployment targets\n") - b.WriteString("4. Run " + c.styles.emphasize.Render("nitric dev") + " to start the development server\n") - b.WriteString("5. Run " + c.styles.emphasize.Render("nitric build") + " to build the project for a specific platform\n") + b.WriteString("4. Run " + c.styles.emphasize.Render(version.GetCommand("dev")) + " to start the development server\n") + b.WriteString("5. Run " + c.styles.emphasize.Render(version.GetCommand("build")) + " to build the project for a specific platform\n") b.WriteString("\n") b.WriteString("For more information, see the " + c.styles.emphasize.Render("nitric docs") + " at " + c.styles.emphasize.Render("https://nitric.io/docs")) @@ -352,7 +352,7 @@ func (c *NitricApp) Build() error { platformRepository := platforms.NewPlatformRepository(c.apiClient) if len(appSpec.Targets) == 0 { - nitricEdit := style.Teal("nitric edit") + nitricEdit := c.styles.emphasize.Render(version.GetCommand("edit")) fmt.Printf("No targets specified in nitric.yaml, run %s to add a target\n", nitricEdit) return nil } @@ -390,7 +390,7 @@ func (c *NitricApp) Build() error { platform, err := terraform.PlatformFromId(c.fs, targetPlatform, platformRepository) if errors.Is(err, terraform.ErrUnauthenticated) { - fmt.Printf("Please login first, using the %s command\n", c.styles.emphasize.Render("nitric login")) + fmt.Printf("Please login first, using the %s command\n", c.styles.emphasize.Render(version.GetCommand("login"))) return nil } else if err != nil { return err diff --git a/cli/pkg/tui/intro.go b/cli/pkg/tui/intro.go index 6b682aa5b..750a3c545 100644 --- a/cli/pkg/tui/intro.go +++ b/cli/pkg/tui/intro.go @@ -9,7 +9,7 @@ import ( "github.com/nitrictech/nitric/cli/internal/version" ) -var nitric = style.Purple(icons.Lightning + " Nitric") +var nitric = style.Purple(icons.Lightning + " " + version.ProductName) var ver = style.Gray(version.GetShortVersion()) func NitricIntro(elements ...string) string { From 0cba65df33d698e22405c897ef30d1ef5342579c Mon Sep 17 00:00:00 2001 From: Tim Holm Date: Mon, 14 Jul 2025 08:26:45 +1000 Subject: [PATCH 4/4] replace additional product name constants --- cli/internal/api/platform.go | 7 ++++--- cli/internal/api/plugin.go | 15 ++++++++------- cli/internal/config/config.go | 3 ++- cli/internal/details/service/details.go | 9 +++++---- cli/internal/simulation/simulation.go | 2 +- cli/pkg/app/nitric.go | 8 ++++---- cli/pkg/client/go.go | 3 ++- cli/pkg/client/python.go | 3 ++- cli/pkg/client/ts.go | 3 ++- cli/pkg/schema/file.go | 17 +++++++++-------- 10 files changed, 39 insertions(+), 31 deletions(-) diff --git a/cli/internal/api/platform.go b/cli/internal/api/platform.go index d671284ad..93db70f6d 100644 --- a/cli/internal/api/platform.go +++ b/cli/internal/api/platform.go @@ -5,6 +5,7 @@ import ( "fmt" "io" + "github.com/nitrictech/nitric/cli/internal/version" "github.com/nitrictech/nitric/engines/terraform" ) @@ -24,18 +25,18 @@ func (c *NitricApiClient) GetPlatform(team, name string, revision int) (*terrafo return nil, ErrNotFound } - return nil, fmt.Errorf("received non 200 response from nitric plugin details endpoint: %d", response.StatusCode) + return nil, fmt.Errorf("received non 200 response from %s plugin details endpoint: %d", version.ProductName, response.StatusCode) } body, err := io.ReadAll(response.Body) if err != nil { - return nil, fmt.Errorf("failed to read response from nitric auth details endpoint: %v", err) + return nil, fmt.Errorf("failed to read response from %s plugin details endpoint: %v", version.ProductName, err) } var platformSpec PlatformRevisionResponse err = json.Unmarshal(body, &platformSpec) if err != nil { - return nil, fmt.Errorf("unexpected response from nitric plugin details endpoint: %v", err) + return nil, fmt.Errorf("unexpected response from %s plugin details endpoint: %v", version.ProductName, err) } return &platformSpec.Content, nil } diff --git a/cli/internal/api/plugin.go b/cli/internal/api/plugin.go index 09ea38596..e348098e1 100644 --- a/cli/internal/api/plugin.go +++ b/cli/internal/api/plugin.go @@ -5,36 +5,37 @@ import ( "fmt" "io" + "github.com/nitrictech/nitric/cli/internal/version" "github.com/nitrictech/nitric/engines/terraform" ) // FIXME: Because of the difference in fields between identity and resource plugins we need to return an interface -func (c *NitricApiClient) GetPluginManifest(team, lib, version, name string) (interface{}, error) { - response, err := c.get(fmt.Sprintf("/api/plugin_libraries/%s/%s/versions/%s/plugins/%s", team, lib, version, name), true) +func (c *NitricApiClient) GetPluginManifest(team, lib, libVersion, name string) (interface{}, error) { + response, err := c.get(fmt.Sprintf("/api/plugin_libraries/%s/%s/versions/%s/plugins/%s", team, lib, libVersion, name), true) if err != nil { - return nil, fmt.Errorf("failed to connect to nitric auth details endpoint: %v", err) + return nil, fmt.Errorf("failed to connect to %s plugin details endpoint: %v", version.ProductName, err) } if response.StatusCode != 200 { - return nil, fmt.Errorf("received non 200 response from nitric plugin details endpoint: %d", response.StatusCode) + return nil, fmt.Errorf("received non 200 response from %s plugin details endpoint: %d", version.ProductName, response.StatusCode) } body, err := io.ReadAll(response.Body) if err != nil { - return nil, fmt.Errorf("failed to read response from nitric auth details endpoint: %v", err) + return nil, fmt.Errorf("failed to read response from %s plugin details endpoint: %v", version.ProductName, err) } var pluginManifest terraform.ResourcePluginManifest err = json.Unmarshal(body, &pluginManifest) if err != nil { - return nil, fmt.Errorf("unexpected response from nitric plugin details endpoint: %v", err) + return nil, fmt.Errorf("unexpected response from %s plugin details endpoint: %v", version.ProductName, err) } if pluginManifest.Type == "identity" { var identityPluginManifest terraform.IdentityPluginManifest err = json.Unmarshal(body, &identityPluginManifest) if err != nil { - return nil, fmt.Errorf("unexpected response from nitric plugin details endpoint: %v", err) + return nil, fmt.Errorf("unexpected response from %s plugin details endpoint: %v", version.ProductName, err) } return &identityPluginManifest, nil diff --git a/cli/internal/config/config.go b/cli/internal/config/config.go index cdeae78f5..23090987d 100644 --- a/cli/internal/config/config.go +++ b/cli/internal/config/config.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/mitchellh/mapstructure" + "github.com/nitrictech/nitric/cli/internal/version" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -40,7 +41,7 @@ func (c *Config) SetValue(key, value string) error { func (c *Config) GetNitricServerUrl() *url.URL { nitricUrl, err := url.Parse(c.NitricServerUrl) if err != nil { - fmt.Printf("Error parsing nitric server url from config, using default: %v\n", err) + fmt.Printf("Error parsing %s server url from config, using default: %v\n", version.ProductName, err) return &url.URL{ Scheme: "https", Host: "app.nitric.io", diff --git a/cli/internal/details/service/details.go b/cli/internal/details/service/details.go index 45574c55b..843d3ed59 100644 --- a/cli/internal/details/service/details.go +++ b/cli/internal/details/service/details.go @@ -10,6 +10,7 @@ import ( "github.com/nitrictech/nitric/cli/internal/config" detail "github.com/nitrictech/nitric/cli/internal/details" + "github.com/nitrictech/nitric/cli/internal/version" "github.com/samber/do/v2" ) @@ -45,20 +46,20 @@ func (s *Service) GetWorkOSDetails() (*detail.WorkOSDetails, error) { response, err := http.DefaultClient.Do(req) if err != nil { if strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "connection reset by peer") { - return nil, fmt.Errorf("failed to connect to the Nitric API. Please check your connection and try again. If the problem persists, please contact support.") + return nil, fmt.Errorf("failed to connect to the %s API. Please check your connection and try again. If the problem persists, please contact support.", version.ProductName) } - return nil, fmt.Errorf("failed to connect to nitric auth details endpoint: %v", err) + return nil, fmt.Errorf("failed to connect to %s auth details endpoint: %v", version.ProductName, err) } body, err := io.ReadAll(response.Body) if err != nil { - return nil, fmt.Errorf("failed to read response from nitric auth details endpoint: %v", err) + return nil, fmt.Errorf("failed to read response from %s auth details endpoint: %v", version.ProductName, err) } var authDetails AuthDetails err = json.Unmarshal(body, &authDetails) if err != nil { - return nil, fmt.Errorf("unexpected response from nitric auth details endpoint: %v", err) + return nil, fmt.Errorf("unexpected response from %s auth details endpoint: %v", version.ProductName, err) } return &authDetails.WorkOS, nil diff --git a/cli/internal/simulation/simulation.go b/cli/internal/simulation/simulation.go index 7b252b44d..ee8de79f7 100644 --- a/cli/internal/simulation/simulation.go +++ b/cli/internal/simulation/simulation.go @@ -41,7 +41,7 @@ type SimulationServer struct { const DEFAULT_SERVER_PORT = "50051" -var nitric = style.Purple(icons.Lightning + " Nitric") +var nitric = style.Purple(icons.Lightning + " " + version.ProductName) func nitricIntro(addr string, dashUrl string, appSpec *schema.Application) string { version := version.GetShortVersion() diff --git a/cli/pkg/app/nitric.go b/cli/pkg/app/nitric.go index 3c1fa81c3..ecc50d236 100644 --- a/cli/pkg/app/nitric.go +++ b/cli/pkg/app/nitric.go @@ -159,11 +159,11 @@ func (c *NitricApp) Init() error { fmt.Println() fmt.Println(c.styles.success.Render(" " + icons.Check + " Project initialized!")) - fmt.Println(c.styles.faint.Render(" nitric project written to " + nitricYamlPath)) + fmt.Println(c.styles.faint.Render(" " + version.ProductName + " project written to " + nitricYamlPath)) fmt.Println() fmt.Println("Next steps:") - fmt.Println("1. Run", c.styles.emphasize.Render(version.GetCommand("edit")), "to start the nitric editor") + fmt.Println("1. Run", c.styles.emphasize.Render(version.GetCommand("edit")), "to start the", version.ProductName, "editor") fmt.Println("2. Design your app's resources and deployment targets") fmt.Println("3. Optionally, use", c.styles.emphasize.Render(version.GetCommand("generate")), "to generate the client libraries for your app") fmt.Println("4. Run", c.styles.emphasize.Render(version.GetCommand("dev")), "to start the development server") @@ -331,12 +331,12 @@ func (c *NitricApp) New(projectName string, force bool) error { b.WriteString("\n") b.WriteString("Next steps:\n") b.WriteString("1. Run " + c.styles.emphasize.Render("cd ./"+projectDir) + " to move to the project directory\n") - b.WriteString("2. Run " + c.styles.emphasize.Render(version.GetCommand("edit")) + " to start the nitric editor\n") + b.WriteString("2. Run " + c.styles.emphasize.Render(version.GetCommand("edit")) + " to start the " + version.ProductName + " editor\n") b.WriteString("3. Design your app's resources and deployment targets\n") b.WriteString("4. Run " + c.styles.emphasize.Render(version.GetCommand("dev")) + " to start the development server\n") b.WriteString("5. Run " + c.styles.emphasize.Render(version.GetCommand("build")) + " to build the project for a specific platform\n") b.WriteString("\n") - b.WriteString("For more information, see the " + c.styles.emphasize.Render("nitric docs") + " at " + c.styles.emphasize.Render("https://nitric.io/docs")) + b.WriteString("For more information, see the " + c.styles.emphasize.Render(version.ProductName+" docs") + " at " + c.styles.emphasize.Render("https://nitric.io/docs")) fmt.Println(b.String()) return nil diff --git a/cli/pkg/client/go.go b/cli/pkg/client/go.go index 163d2e670..411a041c4 100644 --- a/cli/pkg/client/go.go +++ b/cli/pkg/client/go.go @@ -9,6 +9,7 @@ import ( _ "embed" + "github.com/nitrictech/nitric/cli/internal/version" "github.com/nitrictech/nitric/cli/pkg/schema" "github.com/spf13/afero" ) @@ -57,7 +58,7 @@ func GenerateGo(fs afero.Fs, appSpec schema.Application, outputDir string, goPac tmpl := template.Must(template.New("client").Parse(clientTemplate)) data, err := AppSpecToGoTemplateData(appSpec, goPackageName) if err != nil { - return fmt.Errorf("failed to convert nitric application spec into Go SDK template data: %w", err) + return fmt.Errorf("failed to convert %s application spec into Go SDK template data: %w", version.ProductName, err) } var buf bytes.Buffer diff --git a/cli/pkg/client/python.go b/cli/pkg/client/python.go index 5f238dee7..665c96d87 100644 --- a/cli/pkg/client/python.go +++ b/cli/pkg/client/python.go @@ -8,6 +8,7 @@ import ( _ "embed" + "github.com/nitrictech/nitric/cli/internal/version" "github.com/nitrictech/nitric/cli/pkg/schema" "github.com/spf13/afero" ) @@ -49,7 +50,7 @@ func GeneratePython(fs afero.Fs, appSpec schema.Application, outputDir string) e tmpl := template.Must(template.New("client").Parse(pyClientTemplate)) data, err := AppSpecToPyTemplateData(appSpec) if err != nil { - return fmt.Errorf("failed to convert nitric application spec into Python SDK template data: %w", err) + return fmt.Errorf("failed to convert %s application spec into Python SDK template data: %w", version.ProductName, err) } var buf bytes.Buffer diff --git a/cli/pkg/client/ts.go b/cli/pkg/client/ts.go index 64e69a24c..af17edf76 100644 --- a/cli/pkg/client/ts.go +++ b/cli/pkg/client/ts.go @@ -8,6 +8,7 @@ import ( _ "embed" + "github.com/nitrictech/nitric/cli/internal/version" "github.com/nitrictech/nitric/cli/pkg/schema" "github.com/spf13/afero" ) @@ -50,7 +51,7 @@ func GenerateTypeScript(fs afero.Fs, appSpec schema.Application, outputDir strin tmpl := template.Must(template.New("client").Parse(tsClientTemplate)) data, err := AppSpecToTSTemplateData(appSpec) if err != nil { - return fmt.Errorf("failed to convert nitric application spec into TypeScript SDK template data: %w", err) + return fmt.Errorf("failed to convert %s application spec into TypeScript SDK template data: %w", version.ProductName, err) } var buf bytes.Buffer diff --git a/cli/pkg/schema/file.go b/cli/pkg/schema/file.go index 5982927f9..67ca560f0 100644 --- a/cli/pkg/schema/file.go +++ b/cli/pkg/schema/file.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/nitrictech/nitric/cli/internal/version" "github.com/spf13/afero" "github.com/xeipuuv/gojsonschema" "gopkg.in/yaml.v3" @@ -11,14 +12,14 @@ import ( func LoadFromFile(fs afero.Fs, path string, validate bool) (*Application, error) { if exists, err := afero.Exists(fs, path); err != nil { - return nil, fmt.Errorf("nitric application file could not be loaded at path: %s", path) + return nil, fmt.Errorf("%s application file could not be loaded at path: %s", version.ProductName, path) } else if !exists { - return nil, fmt.Errorf("nitric application file not found at path: %s", path) + return nil, fmt.Errorf("%s application file not found at path: %s", version.ProductName, path) } fileData, err := afero.ReadFile(fs, path) if err != nil { - return nil, fmt.Errorf("Error opening nitric application file at path %s: %s", path, err) + return nil, fmt.Errorf("error opening %s application file at path %s: %s", version.ProductName, path, err) } var appSpec *Application @@ -29,11 +30,11 @@ func LoadFromFile(fs afero.Fs, path string, validate bool) (*Application, error) } else if strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml") { appSpec, results, err = ApplicationFromYaml(string(fileData)) } else { - return nil, fmt.Errorf("nitric application file must be a .json or .yaml/.yml file: %s", path) + return nil, fmt.Errorf("%s application file must be a .json or .yaml/.yml file: %s", version.ProductName, path) } if err != nil { - return nil, fmt.Errorf("error parsing nitric application file %s: %s", path, err) + return nil, fmt.Errorf("error parsing %s application file %s: %s", version.ProductName, path, err) } if !validate { @@ -45,12 +46,12 @@ func LoadFromFile(fs afero.Fs, path string, validate bool) (*Application, error) for _, err := range results.Errors() { errs += fmt.Sprintf(" - %s\n", err) } - return nil, fmt.Errorf("invalid nitric application file %s:\n%s", path, errs) + return nil, fmt.Errorf("invalid %s application file %s:\n%s", version.ProductName, path, errs) } // Perform additional validation checks on the application if err := appSpec.IsValid(); err != nil { - return nil, fmt.Errorf("invalid nitric application file %s:\n%s", path, err) + return nil, fmt.Errorf("invalid %s application file %s:\n%s", version.ProductName, path, err) } return appSpec, nil @@ -59,7 +60,7 @@ func LoadFromFile(fs afero.Fs, path string, validate bool) (*Application, error) func SaveToYaml(fs afero.Fs, path string, appSpec *Application) error { yaml, err := yaml.Marshal(appSpec) if err != nil { - return fmt.Errorf("error marshalling nitric application file %s: %s", path, err) + return fmt.Errorf("error marshalling %s application file %s: %s", version.ProductName, path, err) } return afero.WriteFile(fs, path, yaml, 0644)