Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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, ",")))
}

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