Skip to content
Draft
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
20 changes: 20 additions & 0 deletions cli/azd/docs/extensions/extension-framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,26 @@ azd extension source add -n dev -t url -l "https://aka.ms/azd/extensions/registr

Extensions installed from the dev registry are automatically promoted to the main registry when a newer version becomes available there. See the [Dev/Experimental Extension Registry](./extension-resolution-and-versioning.md#devexperimental-extension-registry) section for full details on stability expectations, submission guidelines, promotion behavior, and troubleshooting.

#### Nightly Registry

> [!CAUTION]
> The nightly registry contains automated pre-release builds. They come with **no stability guarantees** and are **not covered by Azure support**. Expect breaking changes between nights.

The nightly registry is an opt-in source containing daily pre-release builds of first-party extensions, published every night from the latest `main`. It lives on the `nightly-registry` branch of the [`azd` GitHub repo](https://github.com/Azure/azure-dev/blob/nightly-registry/cli/azd/extensions/registry.nightly.json) and is **not** configured by default.

To opt-in for the nightly registry run the following command:

```bash
# Add a new extension source named 'nightly' to your `azd` configuration.
azd extension source add -n nightly -t url \
-l "https://raw.githubusercontent.com/Azure/azure-dev/nightly-registry/cli/azd/extensions/registry.nightly.json"

# Install the nightly build of an extension.
azd extension install <extension-id> --source nightly
```

Nightly builds use a `<base>-nightly.<yyyyMMdd>.<buildId>` version format, so the matching stable release always supersedes them — running `azd extension upgrade` moves you back to the `azd` source automatically once that stable version ships. See [Choosing Stable vs. Nightly](./extension-resolution-and-versioning.md#choosing-stable-vs-nightly) for the full workflow, including how to switch channels and opt back out.

#### `azd extension source list`

Displays a list of installed extension sources.
Expand Down
85 changes: 85 additions & 0 deletions cli/azd/docs/extensions/extension-resolution-and-versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,91 @@ If the dev registry URL is unreachable (network issue, DNS failure), operations
azd extension source remove dev
```

## Nightly Extension Registry

The nightly registry is a separate, opt-in extension source that contains automated daily
pre-release builds of first-party extensions. A scheduled pipeline builds each extension from
the latest `main` and publishes a new prerelease version to the registry every night, so the
nightly source always tracks the most recent in-development code.

Like `dev`, the nightly source is **not** configured by default and is **not** a replacement for
the stable `azd` registry. It is a parallel pre-release channel for early adopters who want to
validate upcoming changes before they ship.

| Property | Main Registry | Nightly Registry |
|----------|---------------|------------------|
| Source file | `cli/azd/extensions/registry.json` | `cli/azd/extensions/registry.nightly.json` |
| Location | `https://aka.ms/azd/extensions/registry` | `https://raw.githubusercontent.com/Azure/azure-dev/nightly-registry/cli/azd/extensions/registry.nightly.json` |
| Source name | `azd` (built-in default) | `nightly` (opt-in) |
| Cadence | On stable release | Every night from `main` |
| Stability | Stable releases | **Pre-release; may change or break nightly** |
| Support | Covered by Azure support | **Not covered** |

### Nightly Version Format

Nightly builds are stamped by [`eng/scripts/Set-ExtensionVersionVariable.ps1`](../../../../eng/scripts/Set-ExtensionVersionVariable.ps1)
into a semver-valid prerelease derived from the extension's `version.txt`:

```text
<base>-nightly.<yyyyMMdd>.<buildId> e.g. 0.1.0-nightly.20260618.1234
<base>.nightly.<yyyyMMdd>.<buildId> (when <base> already has a prerelease label)
```

Because these are valid prereleases, semver precedence orders them the way you would expect:

- A stable release always outranks any nightly built from the same base
(`0.1.0-nightly.20260618.1234` < `0.1.0`), so the stable version supersedes the nightly once it ships.
- A later nightly outranks an earlier one, comparing the date and then the build id numerically
(`...20260618.1234` < `...20260618.5678` and `...20260618.9999` < `...20260619.1000`).

### Choosing Stable vs. Nightly

The two channels coexist; you decide per extension which one to install from.

**Stay on stable (default):** Do nothing. Without the `nightly` source configured, every
`azd extension install` and `azd extension upgrade` resolves against the official `azd` registry only.

**Opt in to nightly:** Add the source, then install with `--source nightly`:

```bash
# Add the nightly source once.
azd extension source add -n nightly -t url \
-l "https://raw.githubusercontent.com/Azure/azure-dev/nightly-registry/cli/azd/extensions/registry.nightly.json"

# Install the nightly build of an extension.
azd extension install <extension-id> --source nightly

# Confirm both sources are configured.
azd extension source list
```

When an extension exists in both `azd` and `nightly` and you omit `--source`, `azd` prompts you to
choose (interactive) or errors with `"found in multiple sources"` (non-interactive). See
[Troubleshooting Multi-Registry Scenarios](#troubleshooting-multi-registry-scenarios).

**Switch back to stable:** The nightly source uses the same one-way promotion logic as the dev
registry — see [Upgrade and Dev→Main Promotion](#upgrade-and-devmain-promotion). Because a stable
release always outranks the nightly built from the same base, running `azd extension upgrade` moves
you from `nightly` back to the `azd` source automatically once the corresponding stable version is
published. The extension's stored source flips from `nightly` to `azd`, and it stays on stable
from then on. To force the switch immediately (for example, to leave a nightly that is ahead of the
current stable), reinstall explicitly:

```bash
# --force is required only when the stable version is lower than the installed nightly.
azd extension install <extension-id> --source azd --force
```

**Leave nightly entirely:** Remove the source to stop pulling nightly builds:

```bash
azd extension source remove nightly
```

> [!CAUTION]
> Nightly builds come with **no stability guarantees** and are **not covered by Azure support**.
> Expect breaking changes between nights. Use a stable source for production workflows.

## Related Documentation

| Document | Description |
Expand Down
136 changes: 136 additions & 0 deletions cli/azd/pkg/extensions/update_checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,139 @@ func Test_UpdateChecker_InvalidVersions(t *testing.T) {
require.NoError(t, err)
require.False(t, result.HasUpdate) // Should gracefully handle invalid version
}

// Test_UpdateChecker_NightlyVersions verifies that the update checker correctly
// orders the nightly prerelease versions produced by Set-ExtensionVersionVariable.ps1.
//
// Nightly versions built from a base without an existing prerelease take the shape:
//
// <base>-nightly.<yyyyMMdd>.<buildId> e.g. 0.1.0-nightly.20260618.1234
//
// Several transitions must hold for the upgrade experience to work:
// - nightly -> stable: once the stable <base> ships it must supersede any nightly
// built from that base (semver: a release outranks its prereleases).
// - nightly -> next nightly: a later nightly (newer date, or same date with a
// higher build id) must supersede an earlier one, and an earlier nightly must
// never be offered as an update over a newer one.
// - nightly vs. milestone prereleases: for a clean base, semver orders
// alpha < beta < nightly < rc < stable, so a nightly supersedes alpha/beta of the
// same base but is itself superseded by rc and the final release. When the base
// already carries a prerelease label (e.g. 0.1.0-beta), the appended ".nightly"
// segment outranks every numbered beta of that base, so only the final release
// (or a higher base) supersedes it.
func Test_UpdateChecker_NightlyVersions(t *testing.T) {
tests := []struct {
name string
installed string
available []string
wantUpdate bool
}{
{
name: "nightly to stable",
installed: "0.1.0-nightly.20260618.1234",
available: []string{"0.1.0-nightly.20260618.1234", "0.1.0"},
wantUpdate: true,
},
{
name: "nightly to next nightly newer date",
installed: "0.1.0-nightly.20260618.1234",
available: []string{"0.1.0-nightly.20260618.1234", "0.1.0-nightly.20260619.1234"},
wantUpdate: true,
},
{
name: "nightly to next nightly same date higher build",
installed: "0.1.0-nightly.20260618.1234",
available: []string{"0.1.0-nightly.20260618.1234", "0.1.0-nightly.20260618.5678"},
wantUpdate: true,
},
{
name: "same nightly is not an update",
installed: "0.1.0-nightly.20260618.1234",
available: []string{"0.1.0-nightly.20260618.1234"},
wantUpdate: false,
},
{
name: "older nightly is not offered over newer installed",
installed: "0.1.0-nightly.20260619.1234",
available: []string{"0.1.0-nightly.20260618.1234"},
wantUpdate: false,
},
{
// Clean base: nightly outranks beta of the same base (b < n).
name: "nightly supersedes beta of same base",
installed: "0.1.0-beta.1",
available: []string{"0.1.0-beta.1", "0.1.0-nightly.20260618.1234"},
wantUpdate: true,
},
{
// Clean base: an older beta must never be offered over an installed nightly.
name: "beta is not offered over installed nightly",
installed: "0.1.0-nightly.20260618.1234",
available: []string{"0.1.0-beta.5", "0.1.0-nightly.20260618.1234"},
wantUpdate: false,
},
{
// Clean base: rc outranks nightly of the same base (n < r).
name: "rc supersedes nightly of same base",
installed: "0.1.0-nightly.20260618.1234",
available: []string{"0.1.0-nightly.20260618.1234", "0.1.0-rc.1"},
wantUpdate: true,
},
{
// Prerelease base gotcha: "0.1.0-beta.nightly..." outranks every numbered
// beta of that base because numeric identifiers lose to alphanumeric ones,
// so a later beta.N is NOT offered as an update.
name: "numbered beta does not supersede beta-base nightly",
installed: "0.1.0-beta.nightly.20260618.1234",
available: []string{"0.1.0-beta.2", "0.1.0-beta.nightly.20260618.1234"},
wantUpdate: false,
},
{
// Prerelease base: only the final stable release supersedes the beta-base nightly.
name: "stable supersedes beta-base nightly",
installed: "0.1.0-beta.nightly.20260618.1234",
available: []string{"0.1.0-beta.nightly.20260618.1234", "0.1.0"},
wantUpdate: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tempDir := t.TempDir()
t.Setenv("AZD_CONFIG_DIR", tempDir)

cacheManager, err := NewRegistryCacheManager()
require.NoError(t, err)

ctx := t.Context()
sourceName := "test-source"

versions := make([]ExtensionVersion, 0, len(tt.available))
for _, v := range tt.available {
versions = append(versions, ExtensionVersion{Version: v})
}

err = cacheManager.Set(ctx, sourceName, []*ExtensionMetadata{
{
Id: "test.extension",
DisplayName: "Test Extension",
Versions: versions,
},
})
require.NoError(t, err)

updateChecker := NewUpdateChecker(cacheManager)

extension := &Extension{
Id: "test.extension",
DisplayName: "Test Extension",
Version: tt.installed,
Source: sourceName,
}

result, err := updateChecker.CheckForUpdate(ctx, extension)
require.NoError(t, err)
require.Equal(t, tt.wantUpdate, result.HasUpdate)
})
}
}
1 change: 1 addition & 0 deletions docs/architecture/extension-framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Extensions are discovered from registries — JSON manifests that list available

- **Official registry:** `https://aka.ms/azd/extensions/registry` — Stable, signed, production-ready extensions vetted by the azd team.
- **Dev registry:** `https://aka.ms/azd/extensions/registry/dev` — Experimental and pre-release extensions (unsigned builds, backed by `cli/azd/extensions/registry.dev.json`). Not configured by default; users opt in with `azd extension source add`.
- **Nightly registry:** `https://raw.githubusercontent.com/Azure/azure-dev/nightly-registry/cli/azd/extensions/registry.nightly.json` — Automated daily pre-release builds of first-party extensions, published from the latest `main` and backed by `registry.nightly.json` on the `nightly-registry` branch. Not configured by default; users opt in with `azd extension source add -n nightly`. See [Choosing Stable vs. Nightly](../../cli/azd/docs/extensions/extension-resolution-and-versioning.md#choosing-stable-vs-nightly).
- **Local sources:** File-based manifests for development

The dev registry serves as a staging area for extensions before they graduate to the main registry. Extensions installed from the dev registry are automatically promoted to the main registry when a newer stable version becomes available there. See the [Extension Resolution and Versioning](../../cli/azd/docs/extensions/extension-resolution-and-versioning.md#devexperimental-extension-registry) guide for detailed criteria, stability expectations, and submission guidelines.
Expand Down
Loading
Loading