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
44 changes: 44 additions & 0 deletions cli/docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ azd app run --environment production
| `health` | Monitor health status of services (static or streaming mode) | [→ Full Spec](commands/health.md) |
| `logs` | View logs from running services | [→ Full Spec](commands/logs.md) |
| `info` | Show information about running services | [→ Full Spec](commands/info.md) |
| `hooks` | List the lifecycle hooks configured in azure.yaml | [→ Full Spec](commands/hooks.md) |
| `mcp` | Model Context Protocol server for AI assistant integration | [→ Full Spec](commands/mcp.md) |
| `notifications` | Manage process notifications for service state changes | [→ Full Spec](commands/notifications.md) |
| `version` | Show version information | [→ Full Spec](commands/version.md) |
Expand Down Expand Up @@ -1052,6 +1053,49 @@ api

---

## `azd app hooks`

Reads `azure.yaml` and lists the project-level lifecycle hooks. For each configured hook it shows the command it runs, the shell it uses, and any per-platform override for Windows or POSIX. Use it to confirm what will run around a `run` or `stop` without opening the file.

### Usage

```bash
azd app hooks [flags]
```

### Examples

```bash
# List configured hooks
azd app hooks

# JSON array of hooks
azd app hooks --output json
```

### Flags

| Flag | Short | Type | Default | Description |
|------|-------|------|---------|-------------|
| `--output` | `-o` | string | `text` | Output format: `text` or `json` |

### Lifecycle hooks

| Hook | When it runs |
|------|--------------|
| `prerun` | before services start |
| `postrun` | after services stop following a run |
| `prestop` | before services are stopped |
| `poststop` | after services are stopped |

### Notes

- Hooks are listed in lifecycle order. Hooks that are not configured are omitted.
- A hook with a Windows or POSIX override shows the override command and shell on its own line.
- When no hooks are configured the command prints a short message and exits zero.

---

## `azd app mcp`

Model Context Protocol (MCP) server for AI assistant integration. Enables AI assistants like Claude Desktop and GitHub Copilot to interact with your azd app projects.
Expand Down
79 changes: 79 additions & 0 deletions cli/docs/commands/hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# azd app hooks

List the project-level lifecycle hooks configured in azure.yaml.

## Synopsis

```
azd app hooks [flags]
```

## Description

The `hooks` command reads `azure.yaml` and lists the lifecycle hooks it defines. For each configured hook it shows the command it runs, the shell it uses, whether it continues on error or needs user interaction, and any per-platform override for Windows or POSIX.

Hooks run around the development lifecycle:

| Hook | When it runs |
|------|--------------|
| `prerun` | before services start |
| `postrun` | after services stop following a run |
| `prestop` | before services are stopped |
| `poststop` | after services are stopped |

Use it to confirm what will run around `azd app run` and `azd app stop` without opening the file. When no hooks are configured the command prints a short message and exits zero.

## Flags

| Flag | Description |
|------|-------------|
| `-o, --output` | Output format (text, json) |

## Examples

### List configured hooks

```bash
azd app hooks
```

```
prerun
run: npm run setup
shell: bash
postrun
run: ./cleanup.sh
shell: (default)
```

### Hook with a platform override

A hook that defines a Windows or POSIX override shows it on its own line:

```
prerun
run: ./setup.sh
shell: (default)
windows: setup.ps1 (shell: pwsh)
```

### Get JSON output

```bash
azd app hooks --output json
```

```json
[
{
"name": "prerun",
"run": "npm run setup",
"shell": "bash"
}
]
```

## See Also

- [azd app run](run.md) - Runs prerun and postrun hooks around services
- [azd app stop](stop.md) - Runs prestop and poststop hooks around shutdown
182 changes: 182 additions & 0 deletions cli/src/cmd/app/commands/hooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package commands

import (
"fmt"
"os"

"github.com/jongio/azd-app/cli/src/internal/service"
"github.com/jongio/azd-core/cliout"

"github.com/spf13/cobra"
)

// NewHooksCommand creates the hooks command.
func NewHooksCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "hooks",
Short: "List the lifecycle hooks configured in azure.yaml",
Long: `List the project-level lifecycle hooks configured in azure.yaml.

Shows each configured hook (prerun, postrun, prestop, poststop) with the
command it runs, the shell it uses, and any per-platform override for Windows
or POSIX. Hooks run around the development lifecycle:

prerun before services start
postrun after services stop following a run
prestop before services are stopped
poststop after services are stopped

Examples:
# List configured hooks
azd app hooks

# JSON object keyed by hook name
azd app hooks --output json`,
SilenceUsage: true,
Args: cobra.NoArgs,
RunE: runHooks,
}

return cmd
}

// hookOverride is a per-platform override of a hook.
type hookOverride struct {
Run string `json:"run,omitempty"`
Shell string `json:"shell,omitempty"`
ContinueOnError *bool `json:"continueOnError,omitempty"`
Interactive *bool `json:"interactive,omitempty"`
}

// hookInfo is the resolved view of a single lifecycle hook.
type hookInfo struct {
Name string `json:"name"`
Run string `json:"run"`
Shell string `json:"shell,omitempty"`
ContinueOnError bool `json:"continueOnError,omitempty"`
Interactive bool `json:"interactive,omitempty"`
Windows *hookOverride `json:"windows,omitempty"`
Posix *hookOverride `json:"posix,omitempty"`
}

// lifecycleHookOrder is the stable display order for the four lifecycle hooks.
var lifecycleHookOrder = []string{"prerun", "postrun", "prestop", "poststop"}

func runHooks(_ *cobra.Command, _ []string) error {
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("failed to get current directory: %w", err)
}

azureYaml, err := service.ParseAzureYaml(cwd)
if err != nil {
return fmt.Errorf("failed to load azure.yaml: %w", err)
}

hooks := collectHooks(azureYaml.Hooks)

if cliout.IsJSON() {
return cliout.PrintJSON(hooks)
}

printHooks(hooks)
return nil
}

// collectHooks resolves the configured lifecycle hooks in display order. Hooks
// that are not configured are omitted.
func collectHooks(hooks *service.Hooks) []hookInfo {
sources := map[string]*service.Hook{
"prerun": hooks.GetPrerun(),
"postrun": hooks.GetPostrun(),
"prestop": hooks.GetPrestop(),
"poststop": hooks.GetPoststop(),
}

result := make([]hookInfo, 0, len(lifecycleHookOrder))
for _, name := range lifecycleHookOrder {
h := sources[name]
if h == nil {
continue
}
result = append(result, hookInfo{
Name: name,
Run: h.Run,
Shell: h.Shell,
ContinueOnError: h.ContinueOnError,
Interactive: h.Interactive,
Windows: toHookOverride(h.Windows),
Posix: toHookOverride(h.Posix),
})
}
return result
}

// toHookOverride converts a platform-specific hook to the reporting shape,
// returning nil when there is no override.
func toHookOverride(p *service.PlatformHook) *hookOverride {
if p == nil {
return nil
}
return &hookOverride{
Run: p.Run,
Shell: p.Shell,
ContinueOnError: p.ContinueOnError,
Interactive: p.Interactive,
}
}

// printHooks writes each configured hook and its details in text mode.
func printHooks(hooks []hookInfo) {
if len(hooks) == 0 {
cliout.Info("No lifecycle hooks are configured in azure.yaml")
return
}

cliout.CommandHeader("hooks", "Configured lifecycle hooks")
for i, h := range hooks {
if i > 0 {
cliout.Newline()
}
cliout.Info("%s", h.Name)
cliout.Label("run", shellOrNote(h.Run))
cliout.Label("shell", shellOrDefault(h.Shell))
if h.ContinueOnError {
cliout.Label("continueOnError", "true")
}
if h.Interactive {
cliout.Label("interactive", "true")
}
if h.Windows != nil {
cliout.Label("windows", overrideSummary(h.Windows))
}
if h.Posix != nil {
cliout.Label("posix", overrideSummary(h.Posix))
}
}
}

// shellOrDefault returns the shell name, or a note when the default shell is used.
func shellOrDefault(shell string) string {
if shell == "" {
return "(default)"
}
return shell
}

// shellOrNote returns the command, or a note when no command is set.
func shellOrNote(run string) string {
if run == "" {
return "(none)"
}
return run
}

// overrideSummary renders a per-platform override as a short one-line summary.
func overrideSummary(o *hookOverride) string {
summary := shellOrNote(o.Run)
if o.Shell != "" {
summary += fmt.Sprintf(" (shell: %s)", o.Shell)
}
return summary
}
Loading
Loading