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
6 changes: 6 additions & 0 deletions cli/azd/docs/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,9 @@ For tools that are auto-acquired by `azd`, you are able to configure the followi
- `AZD_BICEP_TOOL_PATH`: The Bicep tool override path. The direct path to `bicep` or `bicep.exe`.
- `AZD_GH_TOOL_PATH`: The `gh` tool override path. The direct path to `gh` or `gh.exe`.
- `AZD_PACK_TOOL_PATH`: The `pack` tool override path. The direct path to `pack` or `pack.exe`.

## Debug variables

> **Warning**: Debug variables are unsupported and may change or be removed without notice.

- `AZD_DEBUG_MSAL_CACHE`: If true, logs MSAL cache metadata before and after login and around the first silent token acquisitions, including account identifiers and usernames, while hashing cache keys and token secrets. Requires `--debug` flag to print to terminal.
264 changes: 264 additions & 0 deletions cli/azd/docs/language-hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
# Hooks

Azure Developer CLI hooks support multiple executor types — Bash, PowerShell,
Python (and future JavaScript, TypeScript, .NET). Every hook follows the same
unified lifecycle regardless of its executor: **Prepare → Execute → Cleanup**.

## Supported Executor Types

| Executor | `kind` value | File extension | Status |
|------------|-------------|----------------|--------------|
| Bash | `sh` | `.sh` | ✅ Stable |
| PowerShell | `pwsh` | `.ps1` | ✅ Stable |
| Python | `python` | `.py` | ✅ Phase 1 |
| JavaScript | `js` | `.js` | ✅ Phase 2 |
| TypeScript | `ts` | `.ts` | ✅ Phase 3 |
| .NET (C#) | `dotnet` | `.cs` | 🔜 Planned |

## Configuration

Hooks are configured in `azure.yaml` under the `hooks` section at the
project or service level. Two optional fields are available:

### `kind` (string, optional)

Specifies the executor type for the hook. Allowed values:
`sh`, `pwsh`, `js`, `ts`, `python`, `dotnet`.

When omitted, the executor is **auto-detected** from the file extension of the
`run` path. For example, `run: ./hooks/seed.py` automatically selects the
Python executor.

### `dir` (string, optional) — working directory

The working directory (`cwd`) for hook execution. Used as the project context
for dependency installation (e.g. `pip install` from `requirements.txt`) and
builds.

**Automatically inferred** from the directory containing the script referenced
by `run`. For example, `run: hooks/preprovision/main.py` infers the working
directory as `hooks/preprovision/`. Only set `dir` as an override when the
project root differs from the script's directory (e.g. the entry point lives
in a `src/` subdirectory but `requirements.txt` is in the parent).

Relative paths are resolved from the project or service root.

## Examples

### Python hook — auto-detected from .py extension

The simplest way to use a Python hook. The executor is inferred from the `.py`
extension, and the working directory is auto-inferred from the script's location.
Dependencies are installed automatically if a `requirements.txt` or
`pyproject.toml` is found in the script's directory.

```yaml
hooks:
postprovision:
run: ./hooks/seed-database.py
```

### Python hook in a subdirectory (dir auto-inferred)

When the script lives in a subdirectory, the `dir` is automatically set to that
directory. No explicit `dir` field is needed:

```yaml
hooks:
preprovision:
run: hooks/preprovision/main.py
# dir is auto-inferred as hooks/preprovision/
```

### Python hook — explicit kind

When auto-detection is not desired or the file extension is ambiguous, set
the `kind` field explicitly to select the Python executor:

```yaml
hooks:
postprovision:
run: ./hooks/setup.py
kind: python
```

### Python hook with working directory override

When the script lives in a subdirectory but dependencies (`requirements.txt`)
are at the parent level, use `dir` to override the auto-inferred working
directory:

```yaml
hooks:
postprovision:
run: ./tools/scripts/seed.py
dir: ./tools # override: requirements.txt is in ./tools, not ./tools/scripts
```

### Python hook with platform overrides

Use `windows` and `posix` overrides to provide platform-specific hooks:

```yaml
hooks:
postprovision:
windows:
run: ./hooks/setup.ps1
shell: pwsh
posix:
run: ./hooks/setup.py
kind: python
```

### Python hook with secrets

Hooks support the `secrets` field for resolving Azure Key Vault references,
regardless of executor type:

```yaml
hooks:
postprovision:
run: ./hooks/seed-database.py
secrets:
DB_CONNECTION_STRING: DATABASE_URL
```

### JavaScript hook — auto-detected from .js extension

The simplest way to use a JavaScript hook. The executor is inferred from the `.js`
extension. Dependencies are installed automatically if a `package.json` is found
in the script's directory (or a parent directory up to the project root).

```yaml
hooks:
postprovision:
run: ./hooks/seed-database.js
```

### JavaScript hook with package.json

When a `package.json` exists near the script, `npm install` runs automatically
before execution.

```yaml
hooks:
postprovision:
run: ./hooks/seed-database.js
# package.json in ./hooks/ → npm install runs automatically
```

### JavaScript hook — explicit kind

```yaml
hooks:
postprovision:
run: ./hooks/setup
kind: js
```

### JavaScript hook with working directory override

```yaml
hooks:
postprovision:
run: ./tools/scripts/seed.js
dir: ./tools # package.json is in ./tools, not ./tools/scripts
```

### JavaScript hook with platform overrides

```yaml
hooks:
postprovision:
windows:
run: ./hooks/setup.ps1
shell: pwsh
posix:
run: ./hooks/setup.js
kind: js
```

### TypeScript hook — auto-detected from .ts extension

TypeScript hooks use `npx tsx` for zero-config execution. `tsx` handles
TypeScript natively without requiring a separate compilation step, and
supports both ESM and CommonJS modules automatically.

```yaml
hooks:
postprovision:
run: ./hooks/seed-database.ts
```

### TypeScript hook with package.json

When a `package.json` is found, dependencies are installed before execution.
If `tsx` is listed as a dependency, the local version is used; otherwise
`npx` downloads it on demand.

```yaml
hooks:
postprovision:
run: ./hooks/seed-database.ts
# package.json with tsx dependency → uses local tsx
```

### TypeScript hook — explicit kind

```yaml
hooks:
postprovision:
run: ./hooks/setup
kind: ts
```

### Bash hook (existing behavior, unchanged)

Bash hooks continue to work exactly as before. The `kind` field is
optional and defaults to the appropriate shell type:

```yaml
hooks:
preprovision:
run: echo "Provisioning starting..."
shell: sh
```

## How It Works

Every hook follows the unified **Prepare → Execute → Cleanup** lifecycle:

1. **Prepare** — The executor validates prerequisites and performs any
setup. This includes:
- **Kind detection** from the explicit `kind` field, the
`shell` field, or the file extension of the `run` path.
- **Runtime validation** — verifying the required runtime is
installed (e.g. Python 3 for `.py` hooks, pwsh for `.ps1`).
- **Project discovery** — walking up the directory tree from the
script to find project files (`requirements.txt`, `pyproject.toml`,
`package.json`, `*.*proj`). The search stops at the project/service
root boundary.
- **Dependency installation** — creating a virtual environment
(for Python) and installing dependencies from the discovered
project file.
- **Temp file creation** — for inline scripts (Bash/PowerShell
only), writing the script content to a temporary file.
2. **Execute** — The executor runs the hook using the appropriate
runtime (e.g. `python`, `bash`, `pwsh`).
3. **Cleanup** — The executor removes any temporary resources created
during Prepare (e.g. inline script temp files). This runs regardless
of whether Execute succeeded or failed.

## Limitations

- **Inline scripts** are only supported for Bash and PowerShell hooks.
All other executor types must reference a file path.
- **Phase 1** supports Python as a non-shell executor.
**Phase 2** adds JavaScript and **Phase 3** adds TypeScript.
.NET support is planned for a future phase.
- **Virtual environments** (Python) are created in the project directory alongside
the dependency file, following the naming convention `{dirName}_env`.
- **TypeScript** hooks require Node.js 18+ and use `npx tsx` for execution.
If `tsx` is not installed locally, `npx` will download it automatically.
- **Package manager** for JS/TS hooks currently uses npm for dependency
installation. Support for pnpm and yarn may be added in a future release.