99 4. Uploads the entry as a standalone per-extension JSON blob
1010
1111 Each extension writes its own file to avoid race conditions when multiple
12- extension pipelines run concurrently. A separate unification script
13- (Build-UnifiedDailyRegistry.ps1) combines all per-extension entries into
14- the final registry-daily.json.
12+ extension pipelines run concurrently. The azd CLI reads each per-extension
13+ entry directly via the registry source URL.
1514
1615. PARAMETER SanitizedExtensionId
1716 Hyphenated extension id (e.g. azure-ai-agents)
@@ -96,48 +95,43 @@ if ($missingArtifacts.Count -gt 0) {
9695 exit 1
9796}
9897
99- # Read extension.yaml for metadata.
100- # Uses simple line-by-line regex parsing — handles top-level scalar fields,
101- # capabilities list, and providers list. This is intentionally not a full YAML
102- # parser. It works for the known extension.yaml schema where all values are
103- # single-line scalars or simple lists. If extension.yaml grows multi-line
104- # values or complex nesting, switch to powershell-yaml.
105- $extYaml = Get-Content $extYamlPath - Raw
106- $extMeta = @ {}
107- foreach ($line in $extYaml -split " `n " ) {
108- if ($line -match " ^(\w[\w\-]*):\s*(.+)$" ) {
109- $extMeta [$matches [1 ]] = $matches [2 ].Trim().Trim(' "' )
110- }
98+ # Install powershell-yaml for proper YAML parsing
99+ $psModuleHelpers = Join-Path $PSScriptRoot " PSModule-Helpers.ps1"
100+ if (! (Test-Path $psModuleHelpers )) {
101+ # Fallback to repo location when running from source checkout
102+ $psModuleHelpers = Join-Path $PSScriptRoot " ../common/scripts/Helpers/PSModule-Helpers.ps1"
103+ }
104+ if (! (Test-Path $psModuleHelpers )) {
105+ Write-Error " PSModule-Helpers.ps1 not found at $PSScriptRoot or repo fallback path"
106+ exit 1
107+ }
108+ . $psModuleHelpers
109+ Install-ModuleIfNotInstalled " powershell-yaml" " 0.4.7" | Import-Module
110+
111+ # Parse extension.yaml
112+ $extData = ConvertFrom-Yaml (Get-Content $extYamlPath - Raw)
113+ if ($null -eq $extData ) {
114+ Write-Error " Failed to parse extension.yaml at $extYamlPath — file may be empty or malformed"
115+ exit 1
111116}
112117
113- # Parse capabilities list
114- $capabilities = @ ()
115- $inCapabilities = $false
116- foreach ($line in $extYaml -split " `n " ) {
117- if ($line -match " ^capabilities:" ) { $inCapabilities = $true ; continue }
118- if ($inCapabilities -and $line -match " ^\s+-\s+(.+)$" ) {
119- $capabilities += $matches [1 ].Trim()
120- } elseif ($inCapabilities -and $line -match " ^\S" ) {
121- break
118+ $extMeta = @ {}
119+ foreach ($key in @ (' namespace' , ' displayName' , ' description' , ' usage' , ' requiredAzdVersion' )) {
120+ if ($extData.ContainsKey ($key )) {
121+ $extMeta [$key ] = $extData [$key ]
122122 }
123123}
124124
125- # Parse providers list
125+ $capabilities = if ($extData.ContainsKey (' capabilities' )) { @ ($extData [' capabilities' ]) } else { @ () }
126+
126127$providers = @ ()
127- $inProviders = $false
128- $currentProvider = $null
129- foreach ($line in $extYaml -split " `n " ) {
130- if ($line -match " ^providers:" ) { $inProviders = $true ; continue }
131- if ($inProviders -and $line -match " ^\s+-\s+name:\s*(.+)$" ) {
132- if ($currentProvider ) { $providers += $currentProvider }
133- $currentProvider = [ordered ]@ { name = $matches [1 ].Trim() }
134- } elseif ($inProviders -and $currentProvider -and $line -match " ^\s+(\w+):\s*(.+)$" ) {
135- $currentProvider [$matches [1 ].Trim ()] = $matches [2 ].Trim()
136- } elseif ($inProviders -and $line -match " ^\S" ) {
137- break
128+ if ($extData.ContainsKey (' providers' )) {
129+ foreach ($p in $extData [' providers' ]) {
130+ $provider = [ordered ]@ {}
131+ foreach ($k in $p.Keys ) { $provider [$k ] = $p [$k ] }
132+ $providers += $provider
138133 }
139134}
140- if ($currentProvider ) { $providers += $currentProvider }
141135
142136# Validate required fields were parsed
143137$requiredFields = @ (' namespace' , ' displayName' , ' description' , ' usage' )
@@ -176,6 +170,12 @@ foreach ($placeholder in $replacements.Keys) {
176170 $template = $template.Replace ($placeholder , $replacements [$placeholder ])
177171}
178172
173+ # Verify all placeholders were replaced
174+ if ($template -match ' \$\{[A-Za-z0-9_]+\}' ) {
175+ Write-Error " Unreplaced placeholder found: $ ( $matches [0 ]) "
176+ exit 1
177+ }
178+
179179$versionEntry = $template | ConvertFrom-Json
180180
181181# Add capabilities and providers (can't template arrays/objects easily)
@@ -184,7 +184,8 @@ if ($providers.Count -gt 0) {
184184 $versionEntry | Add-Member - NotePropertyName " providers" - NotePropertyValue $providers
185185}
186186
187- # Build the per-extension entry
187+ # Build the per-extension entry wrapped in registry format
188+ # azd ext source add -t url expects { "extensions": [...] }
188189$extEntry = [ordered ]@ {
189190 id = $AzdExtensionId
190191 namespace = $extMeta.namespace
@@ -193,9 +194,11 @@ $extEntry = [ordered]@{
193194 versions = @ ($versionEntry )
194195}
195196
196- # Write per-extension entry and validate JSON
197+ $registry = [ordered ]@ { extensions = @ ($extEntry ) }
198+
199+ # Write registry entry and validate JSON
197200$entryFile = " $AzdExtensionId .json"
198- $extEntry | ConvertTo-Json - Depth 20 | Set-Content $entryFile - Encoding utf8
201+ $registry | ConvertTo-Json - Depth 20 | Set-Content $entryFile - Encoding utf8
199202
200203try {
201204 $null = Get-Content $entryFile - Raw | ConvertFrom-Json - Depth 20
0 commit comments