Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
*.snap text eol=lf
*.txt text eol=lf
*.sh text eol=lf
*.ts text eol=lf
4 changes: 4 additions & 0 deletions cli/azd/.vscode/cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ words:
- tekuri
- jsonschema
- rustc
- figspec
languageSettings:
- languageId: go
ignoreRegExpList:
Expand Down Expand Up @@ -209,6 +210,9 @@ overrides:
- azdcontext
- azapi
- eastus
- filename: cmd/cmd_help.go
words:
- cmdinternal
- filename: .github/copilot-instructions.md
words:
- golines
Expand Down
9 changes: 5 additions & 4 deletions cli/azd/cmd/cmd_help.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"slices"
"strings"

cmdinternal "github.com/azure/azure-dev/cli/azd/internal/cmd"
"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
Expand Down Expand Up @@ -94,10 +95,10 @@ func getCmdHelpDefaultCommands(cmd *cobra.Command) string {

// getCmdHelpDefaultFlags provides the default implementation for displaying the help flags section.
func getCmdHelpDefaultFlags(cmd *cobra.Command) (result string) {
// force the following flags as global flags for display purposes when displaying help.
forceGlobalFlagNames := map[string]struct{}{
"help": {},
"docs": {},
// Force the following flags as global flags for display purposes when displaying help.
forceGlobalFlagNames := make(map[string]struct{}, len(cmdinternal.NonPersistentGlobalFlags))
for _, name := range cmdinternal.NonPersistentGlobalFlags {
forceGlobalFlagNames[name] = struct{}{}
}

forceGlobalFlags := pflag.NewFlagSet("", pflag.ContinueOnError)
Expand Down
57 changes: 57 additions & 0 deletions cli/azd/cmd/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"

"github.com/azure/azure-dev/cli/azd/cmd/actions"
"github.com/azure/azure-dev/cli/azd/internal/figspec"
"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/spf13/cobra"
)
Expand All @@ -17,6 +18,7 @@ const (
shellZsh = "zsh"
shellFish = "fish"
shellPowerShell = "powershell"
shellFig = "fig"
)

func completionActions(root *actions.ActionDescriptor) {
Expand Down Expand Up @@ -90,6 +92,18 @@ See each sub-command's help for details on how to use the generated script.`,
Footer: getCmdCompletionPowerShellHelpFooter,
},
})

figCmd := &cobra.Command{
Short: "Generate Fig autocomplete spec.",
DisableFlagsInUseLine: true,
}
completionGroup.Add(shellFig, &actions.ActionDescriptorOptions{
Command: figCmd,
ActionResolver: newCompletionFigAction,
FlagsResolver: newCompletionFigFlags,
OutputFormats: []output.Format{output.NoneFormat},
DefaultFormat: output.NoneFormat,
})
}

type completionAction struct {
Expand All @@ -113,6 +127,30 @@ func newCompletionPowerShellAction(cmd *cobra.Command) actions.Action {
return &completionAction{shell: shellPowerShell, cmd: cmd}
}

// Fig completion action and flags
type completionFigFlags struct {
includeHidden bool
}

func newCompletionFigFlags(cmd *cobra.Command) *completionFigFlags {
flags := &completionFigFlags{}
cmd.Flags().BoolVar(&flags.includeHidden, "include-hidden", false, "Include hidden commands in the Fig spec")
_ = cmd.Flags().MarkHidden("include-hidden")
return flags
}

type completionFigAction struct {
flags *completionFigFlags
cmd *cobra.Command
}

func newCompletionFigAction(cmd *cobra.Command, flags *completionFigFlags) actions.Action {
return &completionFigAction{
flags: flags,
cmd: cmd,
}
}

func (a *completionAction) Run(ctx context.Context) (*actions.ActionResult, error) {
rootCmd := a.cmd.Root()

Expand All @@ -137,6 +175,25 @@ func (a *completionAction) Run(ctx context.Context) (*actions.ActionResult, erro
return &actions.ActionResult{}, nil
}

func (a *completionFigAction) Run(ctx context.Context) (*actions.ActionResult, error) {
rootCmd := a.cmd.Root()

// Generate the Fig spec
builder := figspec.NewSpecBuilder(a.flags.includeHidden)
spec := builder.BuildSpec(rootCmd)

// Convert to TypeScript
tsCode, err := spec.ToTypeScript()
if err != nil {
return nil, fmt.Errorf("failed to generate Fig spec: %w", err)
}

// Write to stdout
fmt.Fprint(a.cmd.OutOrStdout(), tsCode)

return &actions.ActionResult{}, nil
}

// Help functions for completion commands

func getCmdCompletionBashHelpDescription(cmd *cobra.Command) string {
Expand Down
24 changes: 12 additions & 12 deletions cli/azd/cmd/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func extensionActions(root *actions.ActionDescriptor) *actions.ActionDescriptor
// azd extension show
group.Add("show", &actions.ActionDescriptorOptions{
Command: &cobra.Command{
Use: "show <extension-name>",
Use: "show <extension-id>",
Short: "Show details for a specific extension.",
},
OutputFormats: []output.Format{output.JsonFormat, output.NoneFormat},
Expand All @@ -59,30 +59,30 @@ func extensionActions(root *actions.ActionDescriptor) *actions.ActionDescriptor
FlagsResolver: newExtensionShowFlags,
})

// azd extension install <extension-name>
// azd extension install <extension-id>
group.Add("install", &actions.ActionDescriptorOptions{
Command: &cobra.Command{
Use: "install <extension-name>",
Use: "install <extension-id>",
Short: "Installs specified extensions.",
},
ActionResolver: newExtensionInstallAction,
FlagsResolver: newExtensionInstallFlags,
})

// azd extension uninstall <extension-name>
// azd extension uninstall <extension-id>
group.Add("uninstall", &actions.ActionDescriptorOptions{
Command: &cobra.Command{
Use: "uninstall <extension-name>",
Use: "uninstall [extension-id]",
Short: "Uninstall specified extensions.",
},
ActionResolver: newExtensionUninstallAction,
FlagsResolver: newExtensionUninstallFlags,
})

// azd extension upgrade <extension-name>
// azd extension upgrade <extension-id>
group.Add("upgrade", &actions.ActionDescriptorOptions{
Command: &cobra.Command{
Use: "upgrade <extension-name>",
Use: "upgrade [extension-id]",
Short: "Upgrade specified extensions.",
},
ActionResolver: newExtensionUpgradeAction,
Expand Down Expand Up @@ -234,7 +234,7 @@ func (a *extensionListAction) Run(ctx context.Context) (*actions.ActionResult, e
a.console.Message(ctx, output.WithWarningFormat("WARNING: No extensions installed.\n"))
a.console.Message(ctx, fmt.Sprintf(
"Run %s to install extensions.",
output.WithHighLightFormat("azd extension install <extension-name>"),
output.WithHighLightFormat("azd extension install <extension-id>"),
))
} else {
a.console.Message(ctx, output.WithWarningFormat("WARNING: No extensions found in configured sources.\n"))
Expand Down Expand Up @@ -452,7 +452,7 @@ func (t *extensionShowItem) Display(writer io.Writer) error {

func (a *extensionShowAction) Run(ctx context.Context) (*actions.ActionResult, error) {
if len(a.args) == 0 {
return nil, fmt.Errorf("must specify an extension name")
return nil, fmt.Errorf("must specify an extension id")
}
if len(a.args) > 1 {
return nil, fmt.Errorf("cannot specify multiple extensions")
Expand Down Expand Up @@ -565,7 +565,7 @@ func (a *extensionInstallAction) Run(ctx context.Context) (*actions.ActionResult

extensionIds := a.args
if len(extensionIds) == 0 {
return nil, fmt.Errorf("must specify an extension name")
return nil, fmt.Errorf("must specify an extension id")
}

if len(extensionIds) > 1 && a.flags.version != "" {
Expand Down Expand Up @@ -725,7 +725,7 @@ func (a *extensionUninstallAction) Run(ctx context.Context) (*actions.ActionResu
}

if len(a.args) == 0 && !a.flags.all {
return nil, fmt.Errorf("must specify an extension name or use --all flag")
return nil, fmt.Errorf("must specify an extension id or use --all flag")
}

a.console.MessageUxItem(ctx, &ux.MessageTitle{
Expand Down Expand Up @@ -831,7 +831,7 @@ func (a *extensionUpgradeAction) Run(ctx context.Context) (*actions.ActionResult
}

if len(a.args) == 0 && !a.flags.all {
return nil, fmt.Errorf("must specify an extension name or use --all flag")
return nil, fmt.Errorf("must specify an extension id or use --all flag")
}

a.console.MessageUxItem(ctx, &ux.MessageTitle{
Expand Down
35 changes: 35 additions & 0 deletions cli/azd/cmd/figspec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package cmd

import (
"testing"

"github.com/azure/azure-dev/cli/azd/internal/figspec"
"github.com/azure/azure-dev/cli/azd/test/snapshot"
"github.com/stretchr/testify/require"
)

// TestFigSpec generates a Fig autocomplete spec for azd, powering VS Code terminal IntelliSense.
// The generated TypeScript spec must be committed to the vscode repository to enable completions.
//
// To update snapshots (assuming your current directory is cli/azd):
//
// For Bash,
// UPDATE_SNAPSHOTS=true go test ./cmd -run TestFigSpec
//
// For Pwsh,
// $env:UPDATE_SNAPSHOTS='true'; go test ./cmd -run TestFigSpec; $env:UPDATE_SNAPSHOTS=$null
func TestFigSpec(t *testing.T) {
root := NewRootCmd(false, nil, nil)

builder := figspec.NewSpecBuilder(false)
spec := builder.BuildSpec(root)

typescript, err := spec.ToTypeScript()
require.NoError(t, err)

snapshotter := snapshot.NewConfig(".ts")
snapshotter.SnapshotT(t, typescript)
}
Loading
Loading