Skip to content

Add direct registry source support to azd extension commands#8792

Open
Copilot wants to merge 9 commits into
mainfrom
copilot/add-extension-install-url-support
Open

Add direct registry source support to azd extension commands#8792
Copilot wants to merge 9 commits into
mainfrom
copilot/add-extension-install-url-support

Conversation

Copilot AI commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Resolves #8581

This PR expands azd extension source handling so -s/--source can accept either a registered source name or a registry location. This removes the extra azd extension source add step for interactive install and upgrade flows while preserving the existing registered-source behavior for scripts and users who already manage sources explicitly.

Remote source:

image

Local source:

image

This also makes it possible to query a source's extensions with azd ext list without having to add it:

image

For install and upgrade, direct locations are inferred as URL or file sources, validated before being saved, and registered as persisted sources before extension resolution continues.

If the same location is already registered under any name, azd reuses that source instead of prompting again or creating a duplicate. File locations are normalized to absolute paths so later upgrade, list, and show commands resolve the same registry consistently from different working directories, and --no-prompt continues to reject new source registration with guidance to add the source explicitly first.

For list and show, direct URL and file locations are queried read-only without being saved to user configuration. That keeps inspection commands side-effect free, allows them to work in no-prompt scenarios, and makes the returned source clear by surfacing the queried location in output for unregistered sources. The change also adds -s shorthand for azd extension list --source.

Copilot AI changed the title [WIP] Add support for direct installation of extensions from a registry URL Allow azd extension install to register a source directly from a registry URL/path Jun 24, 2026
Copilot AI requested a review from JeffreyCA June 24, 2026 00:17
@JeffreyCA JeffreyCA force-pushed the copilot/add-extension-install-url-support branch from 2196197 to 187b55c Compare June 25, 2026 17:57
@github-actions github-actions Bot added the area/extensions Extensions (general) label Jun 25, 2026
@JeffreyCA JeffreyCA changed the title Allow azd extension install to register a source directly from a registry URL/path Add direct registry source support to azd extension commands Jun 25, 2026
@JeffreyCA JeffreyCA marked this pull request as ready for review June 25, 2026 18:58
Copilot AI review requested due to automatic review settings June 25, 2026 18:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR expands azd extension source resolution so -s/--source can be either a registered source name or a direct registry location (URL/file path), enabling install/upgrade to register-on-demand while keeping list/show side-effect free.

Changes:

  • Adds support for temporary, non-persisted source filtering via FilterOptions.SourceConfig in the extensions manager.
  • Updates azd extension list/show/install/upgrade to accept direct locations for --source, registering locations for mutating commands and querying read-only for inspection commands.
  • Updates help text/docs/completions and adds targeted tests for direct-location source behavior.
Show a summary per file
File Description
cli/azd/pkg/extensions/manager.go Adds SourceConfig to filter options and enables creating sources from a one-off config for list/show scenarios.
cli/azd/cmd/extension.go Implements direct-location --source handling (register-on-demand for install/upgrade; read-only for list/show) and adds -s shorthand for list.
cli/azd/docs/extensions/extension-framework.md Documents new --source behavior across extension commands, including read-only vs persisted semantics.
cli/azd/cmd/testdata/TestUsage-azd-extension-upgrade.snap Updates upgrade help snapshot for the expanded --source meaning.
cli/azd/cmd/testdata/TestUsage-azd-extension-show.snap Updates show help snapshot for the expanded --source meaning.
cli/azd/cmd/testdata/TestUsage-azd-extension-list.snap Updates list help snapshot and reflects the new -s shorthand.
cli/azd/cmd/testdata/TestUsage-azd-extension-install.snap Updates install help snapshot to describe registering from a location.
cli/azd/cmd/testdata/TestFigSpec.ts Updates Fig completion spec descriptions and adds -s for extension list --source.
cli/azd/cmd/extension_upgrade_test.go Adjusts upgrade tests to pass/construct a SourceManager where needed.
cli/azd/cmd/extension_source_location_test.go Adds new tests for direct URL/file location behavior in list/show/upgrade.
cli/azd/cmd/extension_install_source_test.go Adds tests covering source-kind inference and install-time registration-from-location behavior.
cli/azd/cmd/constructors_coverage3_test.go Updates constructor coverage tests for the new sourceManager parameter.

Copilot's findings

  • Files reviewed: 12/12 changed files
  • Comments generated: 2

Comment thread cli/azd/pkg/extensions/manager.go
Comment thread cli/azd/cmd/extension.go

@jongio jongio left a comment

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.

First review of this PR. The approach of overloading --source to accept both registered names and direct locations is well-designed, with proper separation between read-only commands (list/show query without persisting) and mutating commands (install/upgrade register first). Test coverage is thorough, including edge cases for name validation, location reuse, and no-prompt blocking.

A few observations beyond what's already been raised:

  • normalizeSourceKey in cmd/extension.go duplicates the unexported normalizeKey in pkg/extensions/source_manager.go. If the normalization logic evolves in one place, the other won't track. Consider exporting NormalizeKey from the extensions package to eliminate this.
  • For the show command, when --source is a non-existent name that also doesn't look like a location, the user gets "failed to find extension" rather than "extension source not found" (which list produces). Minor inconsistency in error messaging.
  • inferSourceKind classifies any string ending in .json as a file path. A typo like my-source.json (intending my-source) produces a confusing "failed to validate extension source" error instead of "source not found". Acceptable trade-off for the heuristic, but worth noting.

Comment thread cli/azd/cmd/extension.go Outdated
Comment thread cli/azd/cmd/extension.go Outdated
Comment thread cli/azd/cmd/extension.go
@github-actions

Copy link
Copy Markdown

📋 Milestone: June 2026

This work is tracked for June 2026. The team will review it soon!

@jongio jongio left a comment

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.

Second review pass confirms no blocking issues. The design cleanly separates read-only commands (list/show query a temporary SourceConfig without persisting) from mutating commands (install/upgrade register the source first), and --no-prompt correctly rejects registration with guidance.

My earlier observations (normalizeSourceKey duplication, .json heuristic edge case, source persisting before extension lookup in upgrade) are informational suggestions, not blockers. The test coverage is thorough and addresses the key scenarios: name reuse, blank/invalid name retry loops, URL confirmation flow, relative-path normalization, and no-prompt rejection.

One additional note on the SourceConfig path in manager.go: the shallow copy + Source clearing pattern is correct for preventing the name-based filter from double-filtering when a raw SourceConfig is provided.

@JeffreyCA JeffreyCA force-pushed the copilot/add-extension-install-url-support branch from f4f5e13 to e5a867c Compare June 25, 2026 21:22
@JeffreyCA JeffreyCA requested a review from Copilot June 25, 2026 21:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 13/13 changed files
  • Comments generated: 4

Comment thread cli/azd/cmd/extension.go
Comment thread cli/azd/cmd/extension.go Outdated
Comment thread cli/azd/cmd/extension.go Outdated
Comment thread cli/azd/cmd/extension.go Outdated

@jongio jongio left a comment

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.

Incremental review after new commits (f4f5e13 -> e5a867c).

My earlier observations have been addressed:

ormalizeSourceKey duplication is eliminated. NormalizeSourceKey is now exported from pkg/extensions/source_manager.go and used directly in cmd/extension.go.

  1. The .json heuristic edge case is resolved. inferSourceKind now uses os.Stat to check if the file exists rather than checking the extension. A typo like my-source.json that doesn't exist on disk correctly falls through to "source not found" rather than producing a confusing validation error.

The SourceConfig field added to FilterOptions cleanly separates the read-only (list/show) path from the mutating (install/upgrade) path. The locationsEqual helper handles URL case-insensitivity and platform-aware file comparison correctly. Test coverage for the new code is thorough, covering name validation retries, location reuse, no-prompt blocking, and relative path normalization.

No new blocking issues.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 13/13 changed files
  • Comments generated: 2

Comment thread cli/azd/cmd/extension.go Outdated
Comment thread cli/azd/cmd/extension.go

@jongio jongio left a comment

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.

Incremental review (e5a867c -> f94aba9).

The resolveRegisteredSourceName extraction is clean. It consolidates the "try raw name, then try normalized" pattern into one place, adds proper error discrimination (non-ErrSourceNotFound errors no longer masquerade as "source not found"), and the test coverage for normalized lookups is solid.

One low-severity observation below about a redundant lookup in the read-only filter path.

Comment thread cli/azd/cmd/extension.go Outdated

@hemarina hemarina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice extension to the source handling — accepting either a registered name or a direct location removes a real friction point. The read-only vs. mutating split is clean, help text and usage snapshots are updated, and the reuse-by-location / --no-prompt ordering is correct (already-registered locations are reused before the no-prompt guard, so they still resolve under --no-prompt).

The substantive items I found are already covered in the existing threads (silent empty results for a bad read-only location; Windows case-sensitive file dedupe in locationsEqual; normalizeSourceKey duplicating normalizeKey; the .json typo heuristic in inferSourceKind; and the upgrade registering the source before validating the extension). +1 especially on the Windows-casing dedupe — that one is the most likely to bite in practice.

One small additional note in the same vein as the casing issue:

  • locationsEqual doesn't normalize URLs. URL locations are compared with EqualFold only, so https://host/reg.json vs https://host/reg.json/ (trailing slash) would be treated as different locations and register a duplicate source. Optional, but worth folding into the same normalization fix as the file-path casing comment.

Approving — none of these are blocking.

@kristenwomack

kristenwomack commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Thanks Jeffrey, this is a nice change. Collapsing "add source, then install" into a single azd extension install -s <location> removes a real step for users, and keeping list/show read-only is correct — inspection commands shouldn't mutate user config.

A few thoughts from a PM lens, none blocking:

  1. Error message parity between list and show. For an unknown --source name that doesn't look like a location, list returns "extension source not found" and show returns "failed to find extension." Same user mistake, two different error stories. Worth aligning so support and docs can point to one message.

  2. Duplicate-source hygiene under install/upgrade. The reuse-by-location check is good, but locationsEqual only does EqualFold on URLs and absolute-path compare on files. In practice users will paste https://host/reg.json and https://host/reg.json/, or ./reg.json and C:\Repo\Reg.JSON, and end up with two registered sources that point at the same registry. That clutters config and makes "which source did I install from?" harder to answer later. The same normalization fix Marina and Jon called out covers this — please fold trailing-slash and Windows case handling into one pass.

  3. inferSourceKind .json fallback. The os.Stat change handles the common typo case, but the .json branch still fires when the file doesn't exist and the string happens to end in .json. A user who types my-source.json meaning the registered name my-source still gets a validation error rather than "source not found." Low frequency, but the message is misleading when it happens.

  4. Discoverability. This new behavior is the easier path for most users, but the only place it shows up is --source help text. Consider a one-line note in the azd extension source add help that says -s on install/upgrade accepts a location directly, so people discover it from the command they already know.

  5. Telemetry. If we want to measure whether this actually removes the friction we think it does, we need to distinguish named-source vs direct-location usage on install/upgrade/list/show. If that signal isn't already emitted, worth adding before release so we can report on adoption.

  6. Changelog. The entry reads as internal mechanics. A customer-facing version — "azd extension install, upgrade, list, and show now accept a registry URL or file path directly via --source, no separate source add step required" — lands better in release notes.

Approving the direction. Items 1–3 are worth landing before release; 4–6 are follow-ups.

@JeffreyCA

Copy link
Copy Markdown
Contributor

Pushed a commit addressing the latest round of review feedback:

Unknown --source name now errors for showazd extension show -s <value> previously returned results from an unintended registry when the value was neither a registered source nor a URL/file location. It now returns ErrSourceNotFound, matching extension list's behavior. Added TestExtensionShow_UnknownRegisteredNameErrors.

Unsupported registry schema gives actionable guidance — when a direct --source location points at a registry with an unsupported schema version, the error is now surfaced as an ErrorWithSuggestion (upgrade azd, with an install link) before the source is saved, instead of a generic wrapped error. Covered by TestResolveSourceLocation_UnsupportedSchemaReturnsSuggestion.

Deduplicated source-name resolution — refactored the read-only filter path into a single resolveSourceFilter that returns the resolved name, so list/show no longer call resolveRegisteredSourceName twice for the same value.

Consolidated the unsupported-schema error construction — the "upgrade azd" ErrorWithSuggestion was duplicated across three call sites (two in manager.go, one in cmd/extension.go). Extracted a single extensions.NewUnsupportedRegistrySchemaError constructor and pointed all call sites at it, following the same pattern as the earlier NormalizeSourceKey cleanup.

Telemetry — added the extension.source.kind attribute (none / registered / location) for extension list, show, install, and upgrade, with the field definition, schema/matrix/reference docs, and coverage test all updated in sync.

go build, go vet, gofmt, and the affected pkg/extensions and cmd tests are all passing.

@jongio jongio left a comment

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.

Incremental review of Address feedback (f94aba9 -> 69befef, 1 commit, 10 files).

The resolveSourceFilter consolidation eliminates the redundant lookup I flagged in my prior review. The unified sourceFilterResolution struct cleanly separates the three outcomes (registered name, inferred location, unrecognized value) and the callers are simpler for it.

Verified in this pass:

  • normalizeUrlLocation correctly lowercases scheme/host and trims trailing path slashes without breaking percent-encoded paths or query strings. The test covers the trailing-slash dedup case.
  • sourceArgKind captures the telemetry attribute before any resolution mutates flags.source, so the metric reflects user intent regardless of whether the command succeeds.
  • NewUnsupportedRegistrySchemaError deduplicates the ErrorWithSuggestion wrapping from two call sites in manager.go into one. The new call site in registerSourceFromLocation means users hitting an unsupported schema during interactive install now get the upgrade suggestion (previously they got a generic "failed to validate" message).
  • show now returns ErrSourceNotFound for unknown registered names, matching the list behavior I noted earlier.

No new issues.

@JeffreyCA

Copy link
Copy Markdown
Contributor

/azp run azure-dev - cli

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@JeffreyCA

Copy link
Copy Markdown
Contributor

/azp run azure-dev - cli

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@JeffreyCA

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

…-install-url-support

# Conflicts:
#	cli/azd/cmd/telemetry_test.go

Co-authored-by: JeffreyCA <9157833+JeffreyCA@users.noreply.github.com>

Copilot AI commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

Resolved the merge conflict with main in cli/azd/cmd/telemetry_test.go (4bc10e5). Both branches modified the commandsWithSpecificTelemetry manifest — main added tool uninstall, while this branch added the extension install/list/show/upgrade entries. I merged both sets, keeping the list sorted and gofmt-aligned. Verified with go build ./... and TestCommandTelemetryCoverage.

@JeffreyCA

Copy link
Copy Markdown
Contributor

/azp run azure-dev - cli

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/extensions Extensions (general)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

azd ext install: allow installing directly from a registry URL/location without a separate ext source add step

7 participants