Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions cli/azd/pkg/templates/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package templates

import (
"io"
"slices"
"strings"
"text/tabwriter"

Expand Down Expand Up @@ -35,6 +36,11 @@ type Template struct {
// A list of tags associated with the template
Tags []string `json:"tags"`

// A list of languages supported by the template
//
// As of November 2025, known values include: bicep, php, javascript, dotnetCsharp, typescript, python, nodejs, java
Languages []string `json:"languages,omitempty"`

// Additional metadata about the template
Metadata Metadata `json:"metadata,omitempty"`
}
Expand Down Expand Up @@ -73,3 +79,42 @@ func (t *Template) Display(writer io.Writer) error {

return tabs.Flush()
}

// DisplayLanguages returns a list of languages suitable for display from the associated template Tags.
func (t *Template) DisplayLanguages() []string {
languages := make([]string, 0, len(t.Tags))
for _, lang := range t.Tags {
switch lang {
case "dotnetCsharp":
languages = append(languages, "csharp")
case "nodejs":
languages = append(languages, "nodejs")
case "javascript":
if !slices.Contains(t.Tags, "nodejs") && !slices.Contains(t.Tags, "ts") {
languages = append(languages, "js")
}
case "typescript":
if !slices.Contains(t.Tags, "nodejs") {
languages = append(languages, "ts")
}
case "python", "java":
languages = append(languages, lang)
}
}

return languages
}

// CanonicalPath returns a canonicalized path for the template repository
func (t *Template) CanonicalPath() string {
path := t.RepositoryPath
if after, ok := strings.CutPrefix(path, "https://github.com/"); ok {
path = after
}

if after, ok := strings.CutPrefix(strings.ToLower(path), "azure-samples/"); ok {
path = after
}

return path
}
57 changes: 26 additions & 31 deletions cli/azd/pkg/templates/template_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"

"github.com/azure/azure-dev/cli/azd/pkg/input"
"github.com/azure/azure-dev/cli/azd/pkg/output"
)

var (
Expand Down Expand Up @@ -196,47 +197,41 @@ func PromptTemplate(
return Template{}, fmt.Errorf("prompting for template: %w", err)
}

templateChoices := []*Template{}
duplicateNames := []string{}

// Check for duplicate template names
for _, template := range templates {
hasDuplicateName := slices.ContainsFunc(templateChoices, func(t *Template) bool {
return t.Name == template.Name
})
slices.SortFunc(templates, func(a, b *Template) int {
return strings.Compare(a.Name, b.Name)
})

if hasDuplicateName {
duplicateNames = append(duplicateNames, template.Name)
choices := make([]string, 0, len(templates)+1)
for i, template := range templates {
choice := template.Name
if len(choice) > 70 {
choice = choice[0:67] + "..."
}

templateChoices = append(templateChoices, template)
}

templateNames := make([]string, 0, len(templates)+1)
templateDetails := make([]string, 0, len(templates)+1)

for _, template := range templates {
templateChoice := template.Name

// Disambiguate duplicate template names with source identifier
if slices.Contains(duplicateNames, template.Name) {
templateChoice += fmt.Sprintf(" (%s)", template.Source)
for j, otherTemplate := range templates {
if i != j && strings.EqualFold(template.Name, otherTemplate.Name) {
// Disambiguate duplicate template names with source identifier
choice += fmt.Sprintf(" (%s)", template.Source)
}
}

templateDetails = append(templateDetails, template.RepositoryPath)
languages := template.DisplayLanguages()
path := template.CanonicalPath()

if slices.Contains(templateNames, templateChoice) {
duplicateNames = append(duplicateNames, templateChoice)
}
choices = append(choices, fmt.Sprintf("%s\t%s\t[%s]",
choice,
path,
strings.Join(languages, ",")))
}
Comment on lines +221 to +225
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this design is going to cause lots of line wrapping due to template names + URLs + languages unless user is running in a very wide terminal window.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does wrap in an aligned fashion:
image


templateNames = append(templateNames, templateChoice)
choices, err = output.TabAlign(choices, 3)
if err != nil {
return Template{}, fmt.Errorf("aligning choices: %w", err)
}

selected, err := console.Select(ctx, input.ConsoleOptions{
Message: message,
Options: templateNames,
OptionDetails: templateDetails,
DefaultValue: templateNames[0],
Message: message,
Options: choices,
})

// separate this prompt from the next log
Expand Down
Loading