|
| 1 | +# Extension Resolution and Versioning |
| 2 | + |
| 3 | +This document describes how the Azure Developer CLI (`azd`) resolves extensions from configured sources, selects versions using semantic versioning constraints, checks compatibility with the running `azd` version, and installs artifacts for the current platform. It also provides semantic versioning guidance for extension authors and troubleshooting steps for common issues. |
| 4 | + |
| 5 | +## Extension Sources |
| 6 | + |
| 7 | +### Source Types |
| 8 | + |
| 9 | +Extension sources are manifests that describe the extensions available for installation. Each source has a name, a type, and a location. `azd` supports two source types: |
| 10 | + |
| 11 | +| Type | Location | Description | |
| 12 | +|------|----------|-------------| |
| 13 | +| `url` | HTTP/HTTPS endpoint | Remote JSON manifest fetched over the network. | |
| 14 | +| `file` | Local filesystem path | Local JSON file, useful for development and offline scenarios. | |
| 15 | + |
| 16 | +Sources are configured in `~/.azd/config.json`. You can manage them with the following commands: |
| 17 | + |
| 18 | +```bash |
| 19 | +# List configured sources |
| 20 | +azd extension source list |
| 21 | + |
| 22 | +# Add a URL-based source |
| 23 | +azd extension source add -n my-source -t url -l "https://example.com/extensions.json" |
| 24 | + |
| 25 | +# Add a file-based source |
| 26 | +azd extension source add -n local-dev -t file -l "/path/to/registry.json" |
| 27 | + |
| 28 | +# Remove a source |
| 29 | +azd extension source remove my-source |
| 30 | +``` |
| 31 | + |
| 32 | +### Default Source |
| 33 | + |
| 34 | +When no sources are configured, `azd` automatically creates a default source: |
| 35 | + |
| 36 | +| Property | Value | |
| 37 | +|----------|-------| |
| 38 | +| Name | `azd` | |
| 39 | +| Type | `url` | |
| 40 | +| Location | `https://aka.ms/azd/extensions/registry` | |
| 41 | + |
| 42 | +If you remove this source, you can re-add it manually: |
| 43 | + |
| 44 | +```bash |
| 45 | +azd extension source add -n azd -t url -l "https://aka.ms/azd/extensions/registry" |
| 46 | +``` |
| 47 | + |
| 48 | +### Source Ordering |
| 49 | + |
| 50 | +Sources are sorted **alphabetically by name** — not by insertion order. This means a source named `"alpha"` is always consulted before `"beta"`, regardless of when each was added. |
| 51 | + |
| 52 | +## Resolution Algorithm |
| 53 | + |
| 54 | +When you run a command like `azd extension install <id>`, `azd` resolves the extension through the following steps: |
| 55 | + |
| 56 | +### 1. Load and Sort Sources |
| 57 | + |
| 58 | +All configured sources are loaded from `~/.azd/config.json` and sorted alphabetically by name. If no sources exist, the default `"azd"` source is created automatically. |
| 59 | + |
| 60 | +### 2. Search Across Sources |
| 61 | + |
| 62 | +`azd` searches every source for extensions matching the requested ID. There is **no failover** behavior — if a source is unreachable (network error, missing file), the operation fails immediately with an error. `azd` does not skip unreachable sources and continue to the next one. |
| 63 | + |
| 64 | +### 3. Handle Conflicts |
| 65 | + |
| 66 | +If the same extension ID exists in **two or more sources**, `azd` handles the conflict differently depending on the mode: |
| 67 | + |
| 68 | +- **Interactive mode** — `azd` prompts the user to choose which source to install from. |
| 69 | +- **Non-interactive mode** (`--no-prompt` or CI environments) — `azd` returns an error: |
| 70 | + |
| 71 | + ``` |
| 72 | + The <id> extension was found in multiple sources. |
| 73 | + ``` |
| 74 | + |
| 75 | +To avoid the prompt or error, specify the source explicitly: |
| 76 | + |
| 77 | +```bash |
| 78 | +azd extension install <id> --source <source-name> |
| 79 | +``` |
| 80 | + |
| 81 | +There is no priority or merge logic between sources — the `--source` flag is the only way to disambiguate programmatically. |
| 82 | + |
| 83 | +## Version Constraints |
| 84 | + |
| 85 | +### Constraint Syntax |
| 86 | + |
| 87 | +Version constraints differ between the CLI and `azure.yaml`: |
| 88 | + |
| 89 | +#### CLI `--version` flag |
| 90 | + |
| 91 | +The `azd extension install --version` flag accepts only an **exact version string** or **`latest`** (the default when omitted): |
| 92 | + |
| 93 | +```bash |
| 94 | +# Install an exact version |
| 95 | +azd extension install my.extension --version 1.0.0 |
| 96 | + |
| 97 | +# Install the latest version (default) |
| 98 | +azd extension install my.extension --version latest |
| 99 | +azd extension install my.extension |
| 100 | +``` |
| 101 | + |
| 102 | +#### `azure.yaml` `requiredVersions.extensions` |
| 103 | + |
| 104 | +The `requiredVersions.extensions` section in `azure.yaml` supports the full semver constraint syntax provided by the [Masterminds semver](https://github.com/Masterminds/semver) library: |
| 105 | + |
| 106 | +| Syntax | Example | Matches | |
| 107 | +|--------|---------|---------| |
| 108 | +| Exact | `1.0.0` | Only `1.0.0` | |
| 109 | +| Caret | `^1.2.3` | `>=1.2.3, <2.0.0` | |
| 110 | +| Tilde | `~1.2.3` | `>=1.2.3, <1.3.0` | |
| 111 | +| Range | `>=1.0.0,<2.0.0` | Explicit lower and upper bounds | |
| 112 | +| Latest | `latest` or omitted | Highest available version | |
| 113 | + |
| 114 | +```yaml |
| 115 | +requiredVersions: |
| 116 | + extensions: |
| 117 | + azure.ai.agents: ">=1.0.0" |
| 118 | + microsoft.azd.demo: "latest" |
| 119 | + my.custom.extension: "^2.0.0" |
| 120 | +``` |
| 121 | +
|
| 122 | +### Version Selection |
| 123 | +
|
| 124 | +When multiple versions satisfy the constraint, `azd` selects the **highest** matching version. For example, if versions `1.0.0`, `1.1.0`, and `1.2.0` are available and the constraint is `^1.0.0`, version `1.2.0` is installed. |
| 125 | + |
| 126 | +## azd Version Compatibility |
| 127 | + |
| 128 | +### `requiredAzdVersion` Field |
| 129 | + |
| 130 | +Each extension version can declare a minimum `azd` version via the `requiredAzdVersion` field in its metadata. This field accepts any semver constraint expression (for example, `">= 1.24.0"`). |
| 131 | + |
| 132 | +When `azd` resolves versions, it filters them into compatible and incompatible sets based on the running `azd` version: |
| 133 | + |
| 134 | +- **Compatible**: the running `azd` version satisfies the `requiredAzdVersion` constraint. |
| 135 | +- **Incompatible**: the running `azd` version does not satisfy the constraint. |
| 136 | + |
| 137 | +### Behavior |
| 138 | + |
| 139 | +- `azd` filters out all versions whose `requiredAzdVersion` constraint is not satisfied by the running `azd` version, then selects the **highest remaining compatible version** that also matches the user's version constraint. |
| 140 | +- If a **newer incompatible version** exists beyond the selected version, `azd` shows a **warning** suggesting the user upgrade `azd`. |
| 141 | +- If **no compatible versions** remain after filtering, the install **fails** with guidance to upgrade `azd`. The install also fails if the user explicitly requests a specific version that is incompatible. |
| 142 | +- If `requiredAzdVersion` is **empty or cannot be parsed**, the version is treated as compatible (fail-open). This ensures that extensions without the field remain installable. |
| 143 | + |
| 144 | +## Install Flow |
| 145 | + |
| 146 | +Once a version is resolved, installation proceeds through these steps: |
| 147 | + |
| 148 | +1. **Resolve version** — Apply the version constraint against available versions, filter by `azd` compatibility, and select the highest match. |
| 149 | +2. **Resolve dependencies** — If the extension declares dependencies, resolve each one recursively from the **same source as the parent extension**. Cross-source dependency resolution is not performed. Dependencies use the declared version constraint (or `latest`) but do **not** go through `azd` version compatibility filtering — `requiredAzdVersion` checks are only applied to the top-level extension. |
| 150 | +3. **Match platform artifact** — Find the artifact for the current OS and architecture. `azd` first looks for `<os>/<arch>` (for example, `linux/amd64` or `windows/amd64`). If no exact match is found, it falls back to `<os>` only (for example, `linux` or `windows`). |
| 151 | +4. **Download** — Fetch the artifact from its URL (HTTP/HTTPS) or copy from a local file path. |
| 152 | +5. **Validate checksum** — Verify the downloaded file against the published checksum. Supported algorithms are `sha256` and `sha512`. |
| 153 | +6. **Extract** — Unpack the artifact based on its file type: |
| 154 | + - `.zip` — extracted as a ZIP archive |
| 155 | + - `.tar.gz` — extracted as a gzipped tar archive |
| 156 | + - Other — treated as a raw binary and copied directly |
| 157 | +7. **Set permissions** — On Unix-like systems, set the executable permission on the extension binary. |
| 158 | +8. **Update configuration** — Record the installed extension and version in `~/.azd/config.json` under the `extension.installed` section. |
| 159 | + |
| 160 | +## Declaring Extensions in `azure.yaml` |
| 161 | + |
| 162 | +Projects can declare required extensions and version constraints in `azure.yaml`. When `azd init` runs, it reads this configuration and installs each extension automatically. |
| 163 | + |
| 164 | +### Format |
| 165 | + |
| 166 | +```yaml |
| 167 | +requiredVersions: |
| 168 | + extensions: |
| 169 | + azure.ai.agents: ">=1.0.0" |
| 170 | + microsoft.azd.demo: "latest" |
| 171 | + my.custom.extension: "^2.0.0" |
| 172 | +``` |
| 173 | + |
| 174 | +Each entry maps an extension ID to a version constraint string. The same constraint syntax described in [Version Constraints](#version-constraints) applies here. |
| 175 | + |
| 176 | +### Behavior |
| 177 | + |
| 178 | +- When `azd init` runs, it reads the `requiredVersions.extensions` map and installs each extension with the specified constraint. |
| 179 | +- If the constraint value is `null` or empty, `"latest"` is used (the highest available version is installed). |
| 180 | +- If an extension is already installed (any version), `azd init` **skips it** — it does not check whether the installed version satisfies the configured constraint. |
| 181 | +- `azd init` does **not** apply `requiredAzdVersion` compatibility filtering (unlike `azd extension install`). |
| 182 | + |
| 183 | +> **Note:** These are known limitations in the current implementation and may be addressed in future versions: |
| 184 | +> |
| 185 | +> - `azd init` does not check whether an already-installed extension satisfies the configured version constraint. |
| 186 | +> - `azd init` does not apply `requiredAzdVersion` compatibility filtering. |
| 187 | +> - Dependency (transitive) installation calls `Install()` directly without passing through `requiredAzdVersion` compatibility filtering, so a dependency may be installed even if its `requiredAzdVersion` is not satisfied by the running `azd` version. |
| 188 | + |
| 189 | +## Caching |
| 190 | + |
| 191 | +### Cache Location |
| 192 | + |
| 193 | +`azd` caches source manifests locally to avoid fetching them on every operation: |
| 194 | + |
| 195 | +``` |
| 196 | +~/.azd/cache/extensions/<source-name>.json |
| 197 | +``` |
| 198 | + |
| 199 | +Each source has its own cache file. The filename is derived from the source name by lowercasing it and replacing any characters outside `[a-zA-Z0-9._-]` with `_`. For example, a source named `"My Source!"` would be cached as `my_source_.json`. |
| 200 | + |
| 201 | +### Default TTL |
| 202 | + |
| 203 | +The cache has a default time-to-live (TTL) of **4 hours**. After the TTL expires, the next operation that needs the source manifest triggers a fresh HTTP fetch. |
| 204 | + |
| 205 | +### Overriding the TTL |
| 206 | + |
| 207 | +Set the `AZD_EXTENSION_CACHE_TTL` environment variable to override the default TTL. The value uses Go `time.Duration` format: |
| 208 | + |
| 209 | +```bash |
| 210 | +# Disable caching entirely (always fetch fresh) |
| 211 | +export AZD_EXTENSION_CACHE_TTL=0s |
| 212 | +
|
| 213 | +# Set a 30-minute TTL |
| 214 | +export AZD_EXTENSION_CACHE_TTL=30m |
| 215 | +
|
| 216 | +# Set a 1-hour TTL |
| 217 | +export AZD_EXTENSION_CACHE_TTL=1h |
| 218 | +``` |
| 219 | + |
| 220 | +To clear the cache manually, delete the files in `~/.azd/cache/extensions/`. |
| 221 | + |
| 222 | +## Semantic Versioning Guidance |
| 223 | + |
| 224 | +Extension authors should follow [Semantic Versioning 2.0.0](https://semver.org/) when publishing new versions. Consistent versioning enables consumers to use constraint expressions (caret `^`, tilde `~`, ranges) and trust that updates within a range will not break their workflow. |
| 225 | + |
| 226 | +### Major Version Bump (Breaking Changes) |
| 227 | + |
| 228 | +Increment the **major** version when you make incompatible changes. Examples: |
| 229 | + |
| 230 | +- Remove or rename a CLI command or subcommand |
| 231 | +- Remove or rename a CLI flag |
| 232 | +- Change an output schema in a breaking way (remove fields, change types) |
| 233 | +- Change a required input format incompatibly |
| 234 | +- Drop support for an OS or architecture |
| 235 | +- Remove a declared capability |
| 236 | + |
| 237 | +### Minor Version Bump (New Features) |
| 238 | + |
| 239 | +Increment the **minor** version when you add functionality in a backward-compatible manner. Examples: |
| 240 | + |
| 241 | +- Add a new CLI command or subcommand |
| 242 | +- Add a new CLI flag to an existing command |
| 243 | +- Add new fields to an output schema |
| 244 | +- Add a new lifecycle event handler |
| 245 | +- Add support for a new OS or architecture |
| 246 | +- Add a new capability |
| 247 | + |
| 248 | +### Patch Version Bump (Fixes) |
| 249 | + |
| 250 | +Increment the **patch** version for backward-compatible bug fixes. Examples: |
| 251 | + |
| 252 | +- Fix a bug in existing behavior |
| 253 | +- Improve performance without changing the API |
| 254 | +- Update documentation |
| 255 | +- Update dependencies with no user-facing API change |
| 256 | + |
| 257 | +### Pre-release Versions |
| 258 | + |
| 259 | +Use pre-release suffixes for testing before a stable release: |
| 260 | + |
| 261 | +``` |
| 262 | +2.0.0-alpha.1 |
| 263 | +2.0.0-beta.1 |
| 264 | +2.0.0-rc.1 |
| 265 | +``` |
| 266 | + |
| 267 | +When `latest` is specified (or the version is omitted), `azd` selects the **highest semantic version**, which can be a pre-release if it sorts higher than the latest stable version. For semver range constraints in `azure.yaml`, pre-release versions are generally excluded unless the constraint itself explicitly includes a pre-release identifier. |
| 268 | + |
| 269 | +## Troubleshooting |
| 270 | + |
| 271 | +### Common Errors |
| 272 | + |
| 273 | +| Error | Cause | Fix | |
| 274 | +|-------|-------|-----| |
| 275 | +| *"extension X not found"* | The extension ID is not present in any configured source. | Verify your sources with `azd extension source list`. Check the extension ID spelling. | |
| 276 | +| *"found in multiple sources, specify exact source"* | The extension exists in two or more configured sources. | Use `azd extension install X --source <name>` to specify which source to use. | |
| 277 | +| *"no matching version found"* | The version constraint excludes all available versions. | Check available versions with `azd extension show X`. Relax the constraint. | |
| 278 | +| *"dependency X not found"* | A recursive dependency declared by the extension is missing from all sources. | Ensure the dependency is published to an accessible source. | |
| 279 | +| Stale version installed | The source cache has not expired yet, so `azd` is using an older manifest. | Set `AZD_EXTENSION_CACHE_TTL=0s` or delete files in `~/.azd/cache/extensions/`. | |
| 280 | + |
| 281 | +### Diagnostic Steps |
| 282 | + |
| 283 | +1. **Check configured sources:** |
| 284 | + |
| 285 | + ```bash |
| 286 | + azd extension source list |
| 287 | + ``` |
| 288 | + |
| 289 | +2. **Inspect available versions for an extension:** |
| 290 | + |
| 291 | + ```bash |
| 292 | + azd extension show <extension-id> |
| 293 | + ``` |
| 294 | + |
| 295 | +3. **Force a fresh source fetch:** |
| 296 | + |
| 297 | + ```bash |
| 298 | + export AZD_EXTENSION_CACHE_TTL=0s |
| 299 | + azd extension install <extension-id> |
| 300 | + ``` |
| 301 | + |
| 302 | +4. **Install from a specific source:** |
| 303 | + |
| 304 | + ```bash |
| 305 | + azd extension install <extension-id> --source <source-name> |
| 306 | + ``` |
| 307 | + |
| 308 | +## Related Documentation |
| 309 | + |
| 310 | +| Document | Description | |
| 311 | +|----------|-------------| |
| 312 | +| [Extension Framework](./extension-framework.md) | Architecture overview, source and extension management commands, developing extensions. | |
| 313 | +| [Extension SDK Reference](./extension-sdk-reference.md) | Complete API reference for the `azdext` SDK helpers. | |
| 314 | +| [Extension End-to-End Walkthrough](./extension-e2e-walkthrough.md) | Build a complete extension from scratch. | |
| 315 | +| [Extension Style Guide](./extensions-style-guide.md) | Design guidelines for command integration, flags, and discoverability. | |
0 commit comments