Skip to content

Commit 95416aa

Browse files
wbrezaCopilot
andauthored
docs: document extension resolution algorithm and semver guidance (#7836)
* docs: document extension resolution algorithm and semver guidance Add comprehensive documentation for how azd resolves extensions from sources, including source ordering, version constraint syntax, azd version compatibility, caching behavior, and troubleshooting. Includes semantic versioning guidance for extension authors. Closes #6234 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: address PR review feedback on extension resolution doc Fix accuracy issues identified in review: - Describe interactive source prompt for multi-source conflicts - Correct version constraint behavior (CLI accepts exact/latest only) - Fix compatibility filtering to describe filter-then-warn behavior - Correct dependency resolution to same-source pinning - Document azd init limitations (no version satisfaction check) - Add cache filename sanitization details - Fix prerelease selection behavior for latest Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: clarify dependency resolution bypasses azd version filtering - Update line 149 to note dependencies don't go through requiredAzdVersion filtering - Expand known limitations callout with explicit dependency resolution gap Addresses review feedback from jongio on #7836 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3cc8194 commit 95416aa

2 files changed

Lines changed: 316 additions & 0 deletions

File tree

cli/azd/docs/extensions/extension-framework.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Table of Contents
3232
| [Extension End-to-End Walkthrough](./extension-e2e-walkthrough.md) | Build a complete extension from scratch with root command, MCP server, lifecycle events, and security. |
3333
| [Extension Framework Services](./extension-framework-services.md) | Custom language/framework support via `FrameworkServiceProvider`. |
3434
| [Extension Style Guide](./extensions-style-guide.md) | Design guidelines for command integration, flags, and discoverability. |
35+
| [Extension Resolution and Versioning](./extension-resolution-and-versioning.md) | How azd resolves extensions from sources, version constraint syntax, caching, and semver guidance. |
3536

3637
## Getting Started
3738

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
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

Comments
 (0)