From f51901ba74f1caf9af7cae018f832636f885befa Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Tue, 5 Aug 2025 18:03:46 +1000 Subject: [PATCH 1/2] support project view -f for project view --- pkg/cmd/project/view/view.go | 86 +++++++++++++++++++++++++++++++----- 1 file changed, 75 insertions(+), 11 deletions(-) diff --git a/pkg/cmd/project/view/view.go b/pkg/cmd/project/view/view.go index e70fa6ff..5ea3c04a 100644 --- a/pkg/cmd/project/view/view.go +++ b/pkg/cmd/project/view/view.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/OctopusDeploy/cli/pkg/apiclient" "io" + "strings" "github.com/MakeNowJust/heredoc/v2" "github.com/OctopusDeploy/cli/pkg/constants" @@ -37,6 +38,7 @@ type ViewOptions struct { out io.Writer idOrName string flags *ViewFlags + Command *cobra.Command } func NewCmdView(f factory.Factory) *cobra.Command { @@ -63,6 +65,7 @@ func NewCmdView(f factory.Factory) *cobra.Command { cmd.OutOrStdout(), args[0], viewFlags, + cmd, } return viewRun(opts) @@ -81,27 +84,88 @@ func viewRun(opts *ViewOptions) error { return err } - fmt.Fprintf(opts.out, "%s %s\n", output.Bold(project.Name), output.Dimf("(%s)", project.Slug)) + return output.PrintResource(project, opts.Command, output.Mappers[*projects.Project]{ + Json: func(p *projects.Project) any { + cacBranch := "Not version controlled" + if p.IsVersionControlled { + cacBranch = p.PersistenceSettings.(projects.GitPersistenceSettings).DefaultBranch() + } + + return ProjectAsJson{ + Id: p.GetID(), + Name: p.Name, + Slug: p.Slug, + Description: p.Description, + IsVersionControlled: p.IsVersionControlled, + VersionControlBranch: cacBranch, + WebUrl: opts.Host + p.Links["Web"], + } + }, + Table: output.TableDefinition[*projects.Project]{ + Header: []string{"NAME", "SLUG", "DESCRIPTION", "VERSION CONTROL", "WEB URL"}, + Row: func(p *projects.Project) []string { + description := p.Description + if description == "" { + description = constants.NoDescription + } + + cacBranch := "Not version controlled" + if p.IsVersionControlled { + cacBranch = p.PersistenceSettings.(projects.GitPersistenceSettings).DefaultBranch() + } + + return []string{ + output.Bold(p.Name), + p.Slug, + description, + cacBranch, + output.Blue(opts.Host + p.Links["Web"]), + } + }, + }, + Basic: func(p *projects.Project) string { + return formatProjectForBasic(opts, p) + }, + }) +} +type ProjectAsJson struct { + Id string `json:"Id"` + Name string `json:"Name"` + Slug string `json:"Slug"` + Description string `json:"Description"` + IsVersionControlled bool `json:"IsVersionControlled"` + VersionControlBranch string `json:"VersionControlBranch"` + WebUrl string `json:"WebUrl"` +} + +func formatProjectForBasic(opts *ViewOptions, project *projects.Project) string { + var result strings.Builder + + // header + result.WriteString(fmt.Sprintf("%s %s\n", output.Bold(project.Name), output.Dimf("(%s)", project.Slug))) + + // version control branch cacBranch := "Not version controlled" if project.IsVersionControlled { cacBranch = project.PersistenceSettings.(projects.GitPersistenceSettings).DefaultBranch() } - fmt.Fprintf(opts.out, "Version control branch: %s\n", output.Cyan(cacBranch)) + result.WriteString(fmt.Sprintf("Version control branch: %s\n", output.Cyan(cacBranch))) + + // description if project.Description == "" { - fmt.Fprintln(opts.out, output.Dim(constants.NoDescription)) + result.WriteString(fmt.Sprintln(output.Dim(constants.NoDescription))) } else { - fmt.Fprintln(opts.out, output.Dim(project.Description)) + result.WriteString(fmt.Sprintln(output.Dim(project.Description))) } - + + // footer with web URL url := opts.Host + project.Links["Web"] - - // footer - fmt.Fprintf(opts.out, "View this project in Octopus Deploy: %s\n", output.Blue(url)) - + result.WriteString(fmt.Sprintf("View this project in Octopus Deploy: %s\n", output.Blue(url))) + if opts.flags.Web.Value { browser.OpenURL(url) } - - return nil + + return result.String() } From 8e813c60ef9746712193e02c7e274e7b0bc33aab Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Wed, 6 Aug 2025 12:09:04 +1000 Subject: [PATCH 2/2] fix issue that have double "\\" in the webUrl --- pkg/cmd/project/view/view.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/project/view/view.go b/pkg/cmd/project/view/view.go index 5ea3c04a..d97f74f6 100644 --- a/pkg/cmd/project/view/view.go +++ b/pkg/cmd/project/view/view.go @@ -22,6 +22,13 @@ const ( FlagWeb = "web" ) +// joinURL joins host and path, ensuring there's exactly one slash between them +func joinURL(host, path string) string { + host = strings.TrimSuffix(host, "/") + path = strings.TrimPrefix(path, "/") + return host + "/" + path +} + type ViewFlags struct { Web *flag.Flag[bool] } @@ -98,7 +105,7 @@ func viewRun(opts *ViewOptions) error { Description: p.Description, IsVersionControlled: p.IsVersionControlled, VersionControlBranch: cacBranch, - WebUrl: opts.Host + p.Links["Web"], + WebUrl: joinURL(opts.Host, p.Links["Web"]), } }, Table: output.TableDefinition[*projects.Project]{ @@ -119,7 +126,7 @@ func viewRun(opts *ViewOptions) error { p.Slug, description, cacBranch, - output.Blue(opts.Host + p.Links["Web"]), + output.Blue(joinURL(opts.Host, p.Links["Web"])), } }, }, @@ -160,7 +167,7 @@ func formatProjectForBasic(opts *ViewOptions, project *projects.Project) string } // footer with web URL - url := opts.Host + project.Links["Web"] + url := joinURL(opts.Host, project.Links["Web"]) result.WriteString(fmt.Sprintf("View this project in Octopus Deploy: %s\n", output.Blue(url))) if opts.flags.Web.Value {