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
4 changes: 4 additions & 0 deletions cli/azd/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## 1.30.0-beta.1 (Unreleased)

### Other Changes

Comment thread
hyoshis marked this conversation as resolved.
- Rename the `azd extension upgrade` and `azd tool upgrade` commands to `azd extension update` and `azd tool update`. The former `upgrade` names continue to work as aliases for backward compatibility.
Comment thread
hyoshis marked this conversation as resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You noted below that the JSON output is changing after all. Once that lands, this entry understates the impact: azd extension update -o json and azd tool update -o json change action, upgraded, and dependencyUpgrades, and those changes also hit callers who invoke the commands through the upgrade alias. So "continue to work as aliases for backward compatibility" won't be accurate for scripted consumers, which are exactly the ones the alias is meant to protect.

This repo already has a ### Breaking Changes convention (13 prior releases use it, and it sorts first within a release, see 1.28.0 and 1.23.15).

Suggested change
- Rename the `azd extension upgrade` and `azd tool upgrade` commands to `azd extension update` and `azd tool update`. The former `upgrade` names continue to work as aliases for backward compatibility.
- Rename the `azd extension upgrade` and `azd tool upgrade` commands to `azd extension update` and `azd tool update`. The former `upgrade` names continue to work as command aliases.

Then add a ### Breaking Changes section above ### Other Changes covering the renamed JSON keys, and note they apply to the legacy upgrade aliases too.


## 1.29.0 (2026-07-29)

### Features Added
Expand Down
2 changes: 1 addition & 1 deletion cli/azd/cmd/auto_install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2124,7 +2124,7 @@ func TestProjectExtensionErrorsCarrySuggestions(t *testing.T) {
suggestErr, ok := errors.AsType[*internal.ErrorWithSuggestion](err)
require.True(t, ok, "expected an ErrorWithSuggestion")
assert.Contains(t, suggestErr.Error(), "does not satisfy constraint")
assert.Contains(t, suggestErr.Suggestion, "azd extension upgrade microsoft.foundry")
assert.Contains(t, suggestErr.Suggestion, "azd extension update microsoft.foundry")
assert.NotContains(t, suggestErr.Suggestion, "--version >=1.0.0")
})

Expand Down
115 changes: 58 additions & 57 deletions cli/azd/cmd/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,32 +114,33 @@ tracked for updates; reinstall from a newer bundle to update.`,
FlagsResolver: newExtensionUninstallFlags,
})

// azd extension upgrade <extension-id>
group.Add("upgrade", &actions.ActionDescriptorOptions{
// azd extension update <extension-id>
group.Add("update", &actions.ActionDescriptorOptions{
Command: &cobra.Command{
Use: "upgrade [extension-id]",
Short: "Upgrade installed extensions to the latest version.",
Long: `Upgrade one or more installed extensions.
Use: "update [extension-id]",
Aliases: []string{"upgrade"},
Comment thread
hyoshis marked this conversation as resolved.
Comment thread
hyoshis marked this conversation as resolved.
Comment thread
hyoshis marked this conversation as resolved.
Comment thread
hyoshis marked this conversation as resolved.
Short: "Update installed extensions to the latest version.",
Long: `Update one or more installed extensions.

By default, uses the stored registry source for each extension. If the stored
source is unavailable, falls back to the main (azd) registry. Extensions that
were installed from a non-main registry (e.g., dev) are automatically promoted
to the main registry when a newer version is available there.

Use --source to override the registry source for the upgrade. It accepts a
Use --source to override the registry source for the update. It accepts a
registered source name or registry location (URL or file path); locations are
registered first and the upgraded extension's stored source is updated. Because
registered first and the updated extension's stored source is updated. Because
registration is interactive, locations are rejected under --no-prompt. Use --all
to upgrade all installed extensions in a single batch; failures in one extension
do not prevent the remaining extensions from being upgraded.
to update all installed extensions in a single batch; failures in one extension
do not prevent the remaining extensions from being updated.

When upgrading an extension that has dependencies, any installed
dependencies are automatically upgraded too, to the highest version
When updating an extension that has dependencies, any installed
dependencies are automatically updated too, to the highest version
satisfying the extension's declared constraints. Use
--no-dependency-upgrades to opt out and upgrade only the named
--no-dependency-upgrades to opt out and update only the named
extension.

Use --output json for a structured report of all upgrade results.`,
Use --output json for a structured report of all update results.`,
},
OutputFormats: []output.Format{output.JsonFormat, output.NoneFormat},
DefaultFormat: output.NoneFormat,
Expand Down Expand Up @@ -169,7 +170,7 @@ Use --output json for a structured report of all upgrade results.`,
Use: "add",
Short: "Add an extension source with the specified name",
Long: "Add an extension source with the specified name.\n\n" +
"`azd extension install --source` and `azd extension upgrade --source` also accept " +
"`azd extension install --source` and `azd extension update --source` also accept " +
"a registry URL or file path directly.",
},
ActionResolver: newExtensionSourceAddAction,
Expand Down Expand Up @@ -484,9 +485,9 @@ func (a *extensionListAction) Run(ctx context.Context) (*actions.ActionResult, e

if hasCompatibleUpdates {
a.console.Message(ctx, fmt.Sprintf(
"To upgrade: %s", output.WithHighLightFormat("azd extension upgrade <extension-id>")))
"To update: %s", output.WithHighLightFormat("azd extension update <extension-id>")))
a.console.Message(ctx, fmt.Sprintf(
"To upgrade all: %s", output.WithHighLightFormat("azd extension upgrade --all")))
"To update all: %s", output.WithHighLightFormat("azd extension update --all")))
}

if hasIncompatibleUpdates {
Expand All @@ -507,7 +508,7 @@ func (a *extensionListAction) Run(ctx context.Context) (*actions.ActionResult, e
// Status indicator constants for extension list display.
const (
statusUpToDate = "Up to date"
statusUpgrade = "Upgrade available"
statusUpgrade = "Update available"
statusIncompat = "Incompatible"
statusNotInstall = "Not installed"
)
Expand Down Expand Up @@ -1057,7 +1058,7 @@ func (a *extensionInstallAction) Run(ctx context.Context) (*actions.ActionResult
)
if err != nil {
a.console.StopSpinner(ctx, stepMessage, input.StepFailed)
return nil, wrapDependencyError(fmt.Errorf("failed to upgrade extension: %w", err))
return nil, wrapDependencyError(fmt.Errorf("failed to update extension: %w", err))
}

stepMessage += output.WithGrayFormat(" (%s)", extensionVersion.Version)
Expand Down Expand Up @@ -1137,7 +1138,7 @@ func (a *extensionInstallAction) sourceDisplayLabelForInstalled(source string) s

// versionTransitionVerb returns a capitalized verb phrase describing the move
// from the installed version to the target version: "Reinstall" when they match,
// "Upgrade to <target>" / "Downgrade to <target>" when both parse as semver, and
// "Update to <target>" / "Downgrade to <target>" when both parse as semver, and
// a neutral "Replace with <target>" when ordering is undefined (non-semver tags).
func versionTransitionVerb(installedVersion, targetVersion string) string {
if installedVersion == targetVersion {
Expand All @@ -1150,7 +1151,7 @@ func versionTransitionVerb(installedVersion, targetVersion string) string {
case installedErr == nil && targetErr == nil && targetSemver.LessThan(installedSemver):
return fmt.Sprintf("Downgrade to %s", targetVersion)
case installedErr == nil && targetErr == nil && targetSemver.GreaterThan(installedSemver):
return fmt.Sprintf("Upgrade to %s", targetVersion)
return fmt.Sprintf("Update to %s", targetVersion)
Comment thread
hyoshis marked this conversation as resolved.
default:
return fmt.Sprintf("Replace with %s", targetVersion)
}
Expand Down Expand Up @@ -1852,17 +1853,17 @@ func newExtensionUpgradeFlags(cmd *cobra.Command, global *internal.GlobalCommand
flags := &extensionUpgradeFlags{
global: global,
}
cmd.Flags().StringVarP(&flags.version, "version", "v", "", "The version of the extension to upgrade to")
cmd.Flags().StringVarP(&flags.version, "version", "v", "", "The version of the extension to update to")
cmd.Flags().StringVarP(&flags.source, "source", "s", "",
"The registered source name or registry location (URL or file path) to use for upgrades.")
cmd.Flags().BoolVar(&flags.all, "all", false, "Upgrade all installed extensions")
"The registered source name or registry location (URL or file path) to use for updates.")
cmd.Flags().BoolVar(&flags.all, "all", false, "Update all installed extensions")
cmd.Flags().BoolVar(&flags.noDependencyUpgrades, "no-dependency-upgrades", false,
"Do not upgrade dependencies when upgrading an extension that has dependencies")
"Do not update dependencies when updating an extension that has dependencies")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you add --no-dependency-updates and keep this one as a hidden alias, watch out: pflag has no native flag aliasing. If you register a second BoolVar and MarkHidden or MarkDeprecated it, the two flags get separate backing fields, so a user passing --no-dependency-upgrades sets only the legacy field and any code reading the new field silently ignores it. The flag becomes a no-op, which is worse than removing it outright, since scripts keep passing it and dependencies get updated anyway.

Two ways out:

  1. cmd.Flags().SetNormalizeFunc mapping no-dependency-upgrades to no-dependency-updates. Single backing field, old name keeps parsing, only the new name shows in help.
  2. Keep two fields and reconcile explicitly:
if cmd.Flags().Changed("no-dependency-upgrades") {
	flags.noDependencyUpdates = flags.noDependencyUpgrades
}

MarkDeprecated is already used at cli/azd/extensions/azure.ai.agents/internal/cmd/run.go:110 if you want the warning behavior, but it still needs the reconciliation in option 2. Whichever you pick, a test that passes the legacy flag and asserts dependencies are skipped would lock it down.


return flags
}

// azd extension upgrade
// azd extension update
type extensionUpgradeAction struct {
args []string
flags *extensionUpgradeFlags
Expand Down Expand Up @@ -1902,8 +1903,8 @@ func (a *extensionUpgradeAction) Run(
Err: fmt.Errorf(
"cannot specify both an extension name and --all flag: %w",
internal.ErrInvalidFlagCombination),
Suggestion: "Use either 'azd extension upgrade <id>' " +
"or 'azd extension upgrade --all'.",
Suggestion: "Use either 'azd extension update <id>' " +
"or 'azd extension update --all'.",
}
}

Expand All @@ -1912,7 +1913,7 @@ func (a *extensionUpgradeAction) Run(
Err: fmt.Errorf(
"cannot specify --version with multiple extensions: %w",
internal.ErrInvalidFlagCombination),
Suggestion: "Upgrade one extension at a time when " +
Suggestion: "Update one extension at a time when " +
"using --version.",
}
}
Expand All @@ -1927,18 +1928,18 @@ func (a *extensionUpgradeAction) Run(
if len(a.args) == 0 && !a.flags.all {
return nil, &internal.ErrorWithSuggestion{
Err: internal.ErrNoArgsProvided,
Suggestion: "Run 'azd extension upgrade <extension-id>'" +
" or 'azd extension upgrade --all'.",
Suggestion: "Run 'azd extension update <extension-id>'" +
" or 'azd extension update --all'.",
}
}

isJsonOutput := a.formatter.Kind() == output.JsonFormat

if !isJsonOutput {
a.console.MessageUxItem(ctx, &ux.MessageTitle{
Title: "Upgrade azd extensions " +
"(azd extension upgrade)",
TitleNote: "Upgrades the specified extensions " +
Title: "Update azd extensions " +
"(azd extension update)",
TitleNote: "Updates the specified extensions " +
"on the local machine",
})
}
Expand Down Expand Up @@ -2010,7 +2011,7 @@ loop:
report, a.writer, nil,
); err != nil {
return nil, fmt.Errorf(
"failed to format upgrade report: %w", err,
"failed to format update report: %w", err,
)
}
return upgradeActionResult(results)
Expand Down Expand Up @@ -2066,7 +2067,7 @@ func upgradeVersionResolutionError(extensionId, version, source string) error {
// upgradeRetryCommand returns a retry command that preserves the source and
// version explicitly requested by the user.
func upgradeRetryCommand(extensionId, source, version string) string {
command := fmt.Sprintf("azd extension upgrade %s", extensionId)
command := fmt.Sprintf("azd extension update %s", extensionId)
if source != "" {
command += fmt.Sprintf(" --source %s", source)
}
Expand Down Expand Up @@ -2103,8 +2104,8 @@ func (a *extensionUpgradeAction) upgradeOneExtension(
startTime := time.Now()
baseResult := extensions.UpgradeResult{ExtensionId: extensionId}

// Start a telemetry span for this individual extension upgrade.
ctx, span := tracing.Start(ctx, events.ExtensionUpgradeEvent)
// Start a telemetry span for this individual extension update.
ctx, span := tracing.Start(ctx, events.ExtensionUpdateEvent)
defer func() {
elapsed := time.Since(startTime).Milliseconds()
span.SetAttributes(
Expand All @@ -2118,16 +2119,16 @@ func (a *extensionUpgradeAction) upgradeOneExtension(
fields.ExtensionSource.String(
baseResult.ToSource,
),
fields.ExtensionUpgradeDurationMs.Int64(elapsed),
fields.ExtensionUpgradeOutcome.String(
fields.ExtensionUpdateDurationMs.Int64(elapsed),
fields.ExtensionUpdateOutcome.String(
baseResult.Status.String(),
),
fields.ExtensionDependencyUpgradeCount.Int(
fields.ExtensionDependencyUpdateCount.Int(
extensions.CountDependencyUpgrades(baseResult.DependencyUpgrades),
),
)
if baseResult.Status == extensions.UpgradeStatusFailed {
span.SetStatus(codes.Error, "upgrade.failed")
span.SetStatus(codes.Error, "update.failed")
} else {
span.SetStatus(codes.Ok, "")
}
Expand All @@ -2139,7 +2140,7 @@ func (a *extensionUpgradeAction) upgradeOneExtension(
}

stepMsg := fmt.Sprintf(
"Upgrading %s extension",
"Updating %s extension",
output.WithHighLightFormat(extensionId),
)
if !isJsonOutput {
Expand Down Expand Up @@ -2197,7 +2198,7 @@ func (a *extensionUpgradeAction) upgradeOneExtension(
"reinstall with a newer bundle to update"
if !isJsonOutput {
skipMsg := fmt.Sprintf(
"Upgrading %s extension",
"Updating %s extension",
output.WithHighLightFormat(extensionId),
) + output.WithGrayFormat(
" (Installed from a bundle)",
Expand Down Expand Up @@ -2299,7 +2300,7 @@ func (a *extensionUpgradeAction) upgradeOneExtension(
}
if !isJsonOutput {
skipMsg := fmt.Sprintf(
"Upgrading %s extension",
"Updating %s extension",
output.WithHighLightFormat(extensionId),
) + output.WithGrayFormat(
" (No longer available in any registry)",
Expand Down Expand Up @@ -2439,7 +2440,7 @@ func (a *extensionUpgradeAction) upgradeOneExtension(
baseResult.SkipReason = "already up to date"
if !isJsonOutput {
skipMsg := stepMsg + output.WithGrayFormat(
" (No upgrade available)",
" (No update available)",
)
a.console.StopSpinner(
ctx, skipMsg, input.StepSkipped,
Expand All @@ -2460,13 +2461,13 @@ func (a *extensionUpgradeAction) upgradeOneExtension(
if err != nil {
if isNetworkError(err) {
return fail(fmt.Errorf(
"network error upgrading %s "+
"network error updating %s "+
"(check your connection and retry): %w",
extensionId, err,
))
}
return fail(fmt.Errorf(
"failed to upgrade extension: %w", err,
"failed to update extension: %w", err,
))
}
baseResult.ToVersion = extVersion.Version
Expand All @@ -2493,7 +2494,7 @@ func (a *extensionUpgradeAction) upgradeOneExtension(

if !isJsonOutput {
doneMsg := fmt.Sprintf(
"Upgraded %s extension %s",
"Updated %s extension %s",
output.WithHighLightFormat(extensionId),
output.WithGrayFormat(
"(%s \u2192 %s)",
Expand Down Expand Up @@ -2523,7 +2524,7 @@ func (a *extensionUpgradeAction) displayPromotionWarning(
) {
a.console.StopSpinner(ctx, stepMsg, input.StepWarning)
a.console.Message(ctx, output.WithWarningFormat(
" (!) Warning: Upgraded %s extension (%s \u2192 %s, %s \u2192 %s registry)",
" (!) Warning: Updated %s extension (%s \u2192 %s, %s \u2192 %s registry)",
output.WithHighLightFormat(extensionId),
fromVersion, toVersion,
output.WithHighLightFormat(oldSource),
Expand Down Expand Up @@ -2575,7 +2576,7 @@ func displayDependencyUpgradeResults(
case extensions.UpgradeStatusFailed:
suggestionPadding = len("(x) Failed: ")
console.Message(ctx, fmt.Sprintf(
"%s%s Upgrading %s dependency%s",
"%s%s Updating %s dependency%s",
indent,
output.WithErrorFormat("(x) Failed:"),
output.WithHighLightFormat(child.ExtensionId),
Expand All @@ -2591,7 +2592,7 @@ func displayDependencyUpgradeResults(
case extensions.UpgradeStatusSkipped:
suggestionPadding = len("(-) Skipped: ")
line := fmt.Sprintf(
"%s%s Upgrading %s dependency",
"%s%s Updating %s dependency",
indent,
output.WithGrayFormat("(-) Skipped:"),
output.WithHighLightFormat(child.ExtensionId),
Expand Down Expand Up @@ -2622,7 +2623,7 @@ func dependencyChangeVerb(fromVersion, toVersion string) string {
if to.LessThan(from) {
return "Downgraded"
}
return "Upgraded"
return "Updated"
Comment thread
hyoshis marked this conversation as resolved.
}

// displayUpgradeSummary prints the batch summary line after all
Expand All @@ -2639,7 +2640,7 @@ func displayUpgradeSummary(
depUpgraded := summary.DependencyUpgradesByStatus[extensions.UpgradeStatusUpgraded]
if summary.Upgraded > 0 {
upgradedPart := output.WithSuccessFormat(
"%d upgraded", summary.Upgraded,
"%d updated", summary.Upgraded,
)
if depUpgraded > 0 {
noun := "dependency"
Expand Down Expand Up @@ -2685,7 +2686,7 @@ func displayUpgradeSummary(
console.Message(ctx, fmt.Sprintf(
" Run '%s' to retry failed extensions.",
output.WithHighLightFormat(
"azd extension upgrade <name>",
"azd extension update <name>",
),
))
}
Expand All @@ -2702,19 +2703,19 @@ func upgradeActionResult(
return &actions.ActionResult{
Message: &actions.ResultMessage{
Header: fmt.Sprintf(
"%d of %d extensions failed to upgrade",
"%d of %d extensions failed to update",
summary.Failed, summary.Total,
),
},
}, fmt.Errorf(
"%d of %d extensions failed to upgrade",
"%d of %d extensions failed to update",
summary.Failed, summary.Total,
)
}

return &actions.ActionResult{
Message: &actions.ResultMessage{
Header: "Extensions upgraded successfully",
Header: "Extensions updated successfully",
},
}, nil
}
Expand Down
Loading
Loading