azd core - improve extension pack support#8316
Conversation
Top-level required no longer includes capabilities; root-level anyOf requires either capabilities or dependencies. Pack manifests like azure.ai.foundry now validate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…zure#8254, Azure#8257) - createExtensionFilter honors semver constraints (>=, ^, ~) via a new matchesVersionConstraint helper; falls back to exact match for non-semver tags. Install reuses bestSatisfyingVersion for "highest matching version" selection. - Install dep loop has an in-flight visited-set so true install-time cycles (A→B→A) terminate with a clear error. - Manager.Upgrade(ctx, ext, UpgradeOptions{UpgradeDependencies}) walks the upgraded extension's declared dependencies and recursively upgrades each one to the highest version satisfying the parent's constraint (npm/yarn-style). Sibling-pin conflicts surface as failed entries; --no-dependency-upgrades opts out and emits Skipped diagnostics so the user is not left silently out of sync. - UpgradeResult.DependencyUpgrades exposes the nested tree in JSON output; UpgradeSummary.DependencyUpgrades is a separate recursive counter. Telemetry adds extension.dependency_of and extension.dependency_upgrade_count attributes. - Install/upgrade output renders dependency rows flat under the parent step using the (✓) Done: / (-) Skipped: pattern. Usage and Examples headers are skipped when empty so packs render cleanly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR closes functional gaps preventing “extension packs” (manifests that primarily declare dependencies) from working end-to-end by: relaxing the manifest schema, adding semver-constraint support in extension version selection/filtering, preventing install-time dependency cycles, and implementing cascading dependency upgrades (with UX/JSON/telemetry support) during azd extension upgrade.
Changes:
- Update extension manifest schema/docs to allow packs that declare
dependencieswithout top-levelcapabilities. - Teach extension lookup/version resolution to honor semver constraints (e.g.
>=,^,~) and add an install-time dependency-cycle guard. - Add dependency upgrade cascading to
azd extension upgrade(default-on, with--no-dependency-upgrades), including nested JSON output, telemetry attributes, and improved install/upgrade console output.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| cli/azd/pkg/extensions/upgrade_result.go | Adds nested dependency-upgrade results to the JSON contract and aggregates recursive dependency counts. |
| cli/azd/pkg/extensions/upgrade_result_test.go | Adds test coverage for dependencyUpgrades JSON emission and recursive dependency counting. |
| cli/azd/pkg/extensions/manager.go | Implements semver-constraint matching, best-version resolution, install cycle guard, and cascading dependency upgrades via new UpgradeOptions. |
| cli/azd/pkg/extensions/manager_test.go | Adds tests for constraint matching/filtering, pack dependency constraint install, cycle guard, and cascade-upgrade behaviors (including conflicts). |
| cli/azd/internal/tracing/fields/fields.go | Introduces telemetry attribute keys for dependency upgrade attribution and counts. |
| cli/azd/extensions/extension.schema.json | Adjusts schema requirements so manifests must include either capabilities or dependencies. |
| cli/azd/docs/extensions/extension-framework.md | Updates documentation to reflect the revised manifest requirements (packs can be dependencies-only). |
| cli/azd/cmd/testdata/TestUsage-azd-extension-upgrade.snap | Updates help snapshot to include the new --no-dependency-upgrades flag. |
| cli/azd/cmd/testdata/TestFigSpec.ts | Updates Fig completion spec to include --no-dependency-upgrades. |
| cli/azd/cmd/extension.go | Wires new upgrade options/flag, emits telemetry counts, renders dependency upgrade/install results, and suppresses empty Usage/Examples output. |
3ac0fbc to
9cebc1b
Compare
evaluateDependencyChanges only marked visited[dep.Id] after the recursive upgrade branch, so any path that early-continued (already-best, constraint already satisfied, downgrade-skip, disabled-by-opts) left the dependency unmarked. A later sibling pack that recursively required a different version of the same dependency then bypassed the seen-branch and silently upgraded past the earlier parent's constraint. Move the mark to immediately after the seen-check so every subsequent continue/skip/fail path inherits it. Add a regression test covering the no-op-then-sibling-conflict scenario and correct a stale ~1.0.0 comment in an existing test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Azure Dev CLI Install InstructionsInstall scriptsMacOS/Linux
bash: pwsh: WindowsPowerShell install MSI install Standalone Binary
MSI
Documentationlearn.microsoft.com documentationtitle: Azure Developer CLI reference
|
hemarina
left a comment
There was a problem hiding this comment.
Nice work — this closes a real set of pack gaps and the cycle guard + cascading dependency upgrades are well thought out.
I walked the install and upgrade paths against the latest commit:
- Cycle detection is sound. Traced
A→B→A:installInternaluses avisitedmap propagated through dependency recursion (withdefer deleteso independent installs don't see each other), andevaluateDependencyChangesmarksvisited[dep.Id]before recursing so upgrade-level cycles are bounded too.Test_Install_DependencyCycle_BoundedandTest_Upgrade_DependencyUpgrade_CycleGuardboth lock this behavior. - JSON contract is backward-compatible.
dependencyUpgradesisomitemptyon bothUpgradeResultandUpgradeSummary, and the top-levelupgradedcounter still excludes dependencies as the comment promises — existing consumers see no change. - Pack manifest validation works as intended. Schema correctly enforces
anyOf [capabilities, dependencies]withminItems: 1on dependencies, preventing empty packs. - Pre-release semver guidance is documented. The
>=0.1.0-0workaround is called out inextension-framework.mdso users hitting the Masterminds pre-release behavior have a path forward. - Test coverage is strong — constraint matching, pack install with constraints, cycle bounded, dependency conflicts, downgrade refusal, and cascade-upgrade conflicts all have dedicated tests.
All open Copilot threads look resolved in the latest push. Nothing blocking from me.
Fixes #8252
Fixes #8254
Fixes #8257
This PR closes the known gaps that prevent extension packs like
azure.ai.foundryfrom working end-to-end. It also letsazd extension upgradekeep an extension's dependencies in sync, adds user-facing pack documentation, and tightens the install/upgrade output.What changed
Extension pack manifests validate. The schema no longer requires
capabilitiesat the top level. A manifest can declarecapabilities,dependencies, or both — so a pack that just bundles other extensions is now a valid manifest.Semver version constraints work (#8254). Manifest
dependenciesnow properly support semver ranges (e.g.">=0.1.0","^0.1","~0.1.0-preview") and azd will resolve them to the highest published version that satisfies the range. Resolution also skips dependency versions that declare arequiredAzdVersionincompatible with the running azd, so packs never pull in a dependency that won't run on the user's CLI.--versionis exact for users.azd extension install --versionandazd extension upgrade --versionnow require an exact version (orlatest); constraint operators like>=,^,~,*,||, or wildcards are rejected with a clear error pointing users at manifest dependencies for ranges. Bare identifiers likenightlyanddevare still accepted.Install cycles can no longer hang. A registry that declares a true install-time dependency cycle (
A → B → A) used to recurse forever; install now detects the cycle and returns a clear error.azd extension upgradeupgrades dependencies (#8257). Upgrading an extension (or pack) now also reconciles its declared dependencies to the highest published version that satisfies the parent's constraint and is compatible with the running azd. This matches the behavior users expect from package managers like npm/yarn.Reconciliation runs even when the parent itself is already current, so an unchanged pack can still move its dependencies forward when new matching versions are published:
The new
--no-dependency-upgradesflag opts out and lists each outdated dependency asSkippedso users can see what's now out of sync.Install / upgrade output. Dependencies are rendered under the parent step using the same
(✓) Done/(-) Skippedpattern, withUpgraded/Downgraded/Updatedlabels picked based on the actual version movement. EmptyUsage:andExamples:headers are no longer printed (the common case for packs), so the output isn't cluttered with blank rows.JSON output.
azd extension upgrade --output jsonnow exposes adependencyUpgradesarray on each upgrade record and a recursivedependencyUpgradescounter on the summary. The pre-existingupgradedcounter still reports parent extensions only, so existing consumers see the same semantics.Telemetry. Dependency upgrade spans tag the parent extension id, and the parent upgrade span records the recursive dependency upgrade count, so dashboards can attribute the extra work to the triggering upgrade.
Extension pack docs The extension framework docs now describe extension packs: when to use them, what a pack manifest looks like, how install and upgrade behave (including reconciliation when the pack is unchanged), exact-
--versionsemantics, and constraint guidance for preview versions.Validation
Tested with meta package changes from #8247: