diff --git a/.vscode/cspell.json b/.vscode/cspell.json index a03c2828d4..51a9d6cb52 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -32,6 +32,8 @@ "asyncoperation", "azsdk", "azurecli", + "blocklist", + "blocklists", "bugbug", "checkpointstore", "clippy", @@ -44,6 +46,7 @@ "datetime", "deserializers", "devicecode", + "dnssec", "docsrs", "doctest", "dotenv", @@ -60,6 +63,7 @@ "keyvault", "lldb", "maxresults", + "maxpagesize", "maxsize", "msrc", "msrv", @@ -74,6 +78,7 @@ "posix", "pwsh", "reqwest", + "resourcemanager", "rsplit", "rustfmt", "runtimes", @@ -82,6 +87,7 @@ "schannel", "seekable", "servicebus", + "skiptoken", "spector", "stylesheet", "subclient", @@ -235,4 +241,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/eng/dict/crates.txt b/eng/dict/crates.txt index c39802c590..950e013337 100644 --- a/eng/dict/crates.txt +++ b/eng/dict/crates.txt @@ -1,3 +1,139 @@ +agricultureplatform +contentunderstanding +documentintelligence +healthinsights +radiologyinsights +imageanalysis +apicenter +appcomplianceautomation +appconfiguration +dependencymap +computefleet +azurestackhcivm +billingbenefits +botservice +carbonoptimization +edgeactions +cloudhealth +trustedsigning +contentsafety +cognitivelanguage +textanalytics +questionanswering +jobrouter +computerecommender +computelimit +computeschedule +confidentialledger +connectedcache +containerregistry +containerregistrytasks +containerservicedeploymentsafeguards +containerservicefleet +containerservicenodecustomization +containerservicedeploymentsafeguards +contosowidgetmanager +databasefleetmanager +databasewatcher +databoxedge +dataprotection +dellstorage +devcenter +containerservicesafeguards +deviceprovisioningservices +deviceregistry +devopsinfrastructure +devtestlabs +dnsresolver +durabletask +dynatrace +workloadorchestration +disconnectedoperations +commonedgesitemanageroperations +sitemanager +edgemarketplace +edgeorder +edgezones +elasticsan +eventgrid +systemevents +edgeworkloadsecurity +secretsstoreextension +fileshares +iotfirmwaredefense +guestconfig +guestconfiguration +hardwaresecuritymodules +healthbot +healthdataaiservices +deidentification +healthdataaiservices +selfhelp +hybridconnectivity +hybridkubernetes +pineconevectordb +impactreporting +iotoperations +securitydomain +containerorchestratorruntime +liftrarize +arizeaiobservabilityeval +lambdatesthyperexecute +mongodbatlasa +neonpostgres +qumulo +weightsandbiases +loadtesting +managednetworkfabric +mongodbatlas +liftrweightsandbiases +virtualenclaves +mongocluster +querymetrics +monitoraccounts +mysqlflexibleservers +newrelic +newrelicobservability +notificationhubs +onlineexperimentation +oracledatabase +planetarycomputer +microsoftplaywrighttesting +playwrighttesting +portalservicescopilot +postgresqlhsc +cosmosdbforpostgresql +portalservicescopilotpowerbidedicated +privatedns +programmableconnectivity +purestorageblock +recoveryservices +recoveryservicesbackup +recoveryservicesdatareplication +resourceconnector +deploymentstacks +scvmm +portalservices +powerbidedicated +servicefabricmanagedclusters +servicenetworking +signalr +sqlvirtualmachine +standbypool +storageactions +storagediscovery +storagemover +storagesync +trafficmanager +verifiedid +webpubsub +webpubsubservice +widgetanalytics +migrationdiscovery +migrationdiscoverysap +sapmonitors +workloadssapvirtualinstance + async-lock async-stream async-trait @@ -72,4 +208,4 @@ url uuid zerofrom zip -moka \ No newline at end of file +moka diff --git a/eng/emitter-package-lock.json b/eng/emitter-package-lock.json index 47f18f0794..92ff07e28e 100644 --- a/eng/emitter-package-lock.json +++ b/eng/emitter-package-lock.json @@ -8,13 +8,19 @@ "@azure-tools/typespec-rust": "0.29.0" }, "devDependencies": { + "@azure-tools/typespec-autorest": "~0.62.0", "@azure-tools/typespec-azure-core": "~0.62.0", + "@azure-tools/typespec-azure-resource-manager": "~0.62.0", "@azure-tools/typespec-azure-rulesets": "~0.62.0", "@azure-tools/typespec-client-generator-core": "~0.62.0", + "@azure-tools/typespec-liftr-base": "0.11.0", "@typespec/compiler": "^1.6.0", + "@typespec/events": "~0.76.0", "@typespec/http": "^1.6.0", "@typespec/openapi": "^1.6.0", "@typespec/rest": "~0.76.0", + "@typespec/sse": "~0.76.0", + "@typespec/streams": "~0.76.0", "@typespec/versioning": "~0.76.0", "@typespec/xml": "~0.76.0" } @@ -64,12 +70,37 @@ "node": ">=10.12.0" } }, + "node_modules/@azure-tools/typespec-autorest": { + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@azure-tools/typespec-autorest/-/typespec-autorest-0.62.0.tgz", + "integrity": "sha512-XftwipfGGMk9e3qGzbRMBvVpfIqLMJKc8H+XlPHFymnCfexBniZn4Qu2t8nzOVM9fgOoFDjNDzk8W5lf59U5Dg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@azure-tools/typespec-azure-core": "^0.62.0", + "@azure-tools/typespec-azure-resource-manager": "^0.62.0", + "@azure-tools/typespec-client-generator-core": "^0.62.0", + "@typespec/compiler": "^1.6.0", + "@typespec/http": "^1.6.0", + "@typespec/openapi": "^1.6.0", + "@typespec/rest": "^0.76.0", + "@typespec/versioning": "^0.76.0", + "@typespec/xml": "^0.76.0" + }, + "peerDependenciesMeta": { + "@typespec/xml": { + "optional": true + } + } + }, "node_modules/@azure-tools/typespec-azure-core": { "version": "0.62.0", "resolved": "https://registry.npmjs.org/@azure-tools/typespec-azure-core/-/typespec-azure-core-0.62.0.tgz", "integrity": "sha512-4LIFqNHhKO1/jiCH0U2rfI+yH7vkWcFuwpjNyRTWXw/YghAI2d+aIEwtT4oM8jWeYR3KUQfA6AqGPRCm90AXYA==", "license": "MIT", - "peer": true, "engines": { "node": ">=20.0.0" }, @@ -80,12 +111,11 @@ } }, "node_modules/@azure-tools/typespec-azure-resource-manager": { - "version": "0.62.0", - "resolved": "https://registry.npmjs.org/@azure-tools/typespec-azure-resource-manager/-/typespec-azure-resource-manager-0.62.0.tgz", - "integrity": "sha512-e8lO9DhIkZJ3+1o2VItq1P4gEcy9EyA5G7AhTz8qICCfU23e5xUAUfscDHYH8JAfuO9vYLvCee/MKY01MQJ0vA==", + "version": "0.62.1", + "resolved": "https://registry.npmjs.org/@azure-tools/typespec-azure-resource-manager/-/typespec-azure-resource-manager-0.62.1.tgz", + "integrity": "sha512-sbCwg5Auvm2/fYUWbx3RlQyZGlMoAmhtRjrurgwWzZIBxBJ7sVqgUQktl3WGHAoeJ3qYa2gAIL4j8/xSPwt5kw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "change-case": "~5.4.4", "pluralize": "^8.0.0" @@ -123,7 +153,6 @@ "resolved": "https://registry.npmjs.org/@azure-tools/typespec-client-generator-core/-/typespec-client-generator-core-0.62.0.tgz", "integrity": "sha512-fZilNfvqIW6Jzb97SuM5f+i9p5b0261InQRbQcTbeuYGtb5z5M0v8tuGglE4adU8NqQ1OmEv/oRjQjSeSjlxwA==", "license": "MIT", - "peer": true, "dependencies": { "change-case": "~5.4.4", "pluralize": "^8.0.0", @@ -145,6 +174,12 @@ "@typespec/xml": "^0.76.0" } }, + "node_modules/@azure-tools/typespec-liftr-base": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@azure-tools/typespec-liftr-base/-/typespec-liftr-base-0.11.0.tgz", + "integrity": "sha512-XwHRt6GnmTT51iHHUxyFPts6LnhOE+IkANCkh3lhnDdZjHgr5asA3+NXI8UXHbKmAOLReb+eov8tBoN93aS0Ww==", + "dev": true + }, "node_modules/@azure-tools/typespec-rust": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/@azure-tools/typespec-rust/-/typespec-rust-0.29.0.tgz", @@ -601,7 +636,6 @@ "resolved": "https://registry.npmjs.org/@typespec/compiler/-/compiler-1.6.0.tgz", "integrity": "sha512-yxyV+ch8tnqiuU2gClv/mQEESoFwpkjo6177UkYfV0nVA9PzTg4zVVc7+WIMZk04wiLRRT3H1uc11FB1cwLY3g==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "~7.27.1", "@inquirer/prompts": "^7.4.0", @@ -634,7 +668,6 @@ "resolved": "https://registry.npmjs.org/@typespec/events/-/events-0.76.0.tgz", "integrity": "sha512-mdjYQ5HA3Y4ZeyAEmiIDdRa9hbc/5qey5hU9UCA0gL+YWVYgoqLPbZQQTwqq3smM35+5cWp9GTGPyNHcOoRwOA==", "license": "MIT", - "peer": true, "engines": { "node": ">=20.0.0" }, @@ -647,7 +680,6 @@ "resolved": "https://registry.npmjs.org/@typespec/http/-/http-1.6.0.tgz", "integrity": "sha512-q/JwVw21CF4buE3ZS+xSoy2TKAOwyhZ7g3kdNqCgm69BI5p5GGu+3ZlUA+4Blk8hkt0G8XcIN8fhJP+a4O6KAw==", "license": "MIT", - "peer": true, "engines": { "node": ">=20.0.0" }, @@ -666,7 +698,6 @@ "resolved": "https://registry.npmjs.org/@typespec/openapi/-/openapi-1.6.0.tgz", "integrity": "sha512-KuxYAzfP5ljM0PUhSGclNZgTG0H+kyTQcwn6cf4TKhO72R2QMQmiMtN2plqvzsfkL+TLwad1iZhMWTCAMFAQ4w==", "license": "MIT", - "peer": true, "engines": { "node": ">=20.0.0" }, @@ -680,7 +711,6 @@ "resolved": "https://registry.npmjs.org/@typespec/rest/-/rest-0.76.0.tgz", "integrity": "sha512-6jtQWdcmuKyG9cmqWsJjaq64f6N5B/1DS4X3ZoTNgYhHA27Hnsxo1HZWXcpv7Wl+MxLAZM6kgpML0ugDEZcrYQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=20.0.0" }, @@ -694,7 +724,6 @@ "resolved": "https://registry.npmjs.org/@typespec/sse/-/sse-0.76.0.tgz", "integrity": "sha512-mCd4oAXr0Tt990T2PDjx+6H0jmPHINyCH0XRU2HrWtGW5lG/NQVIs5oOxElc7NGg629HrolfLTw0oW8hdMD7Eg==", "license": "MIT", - "peer": true, "engines": { "node": ">=20.0.0" }, @@ -710,7 +739,6 @@ "resolved": "https://registry.npmjs.org/@typespec/streams/-/streams-0.76.0.tgz", "integrity": "sha512-7gQPtsokyn0Mjr43MAik6ZkQt1PZjseU+KcBE2iGT9P6oWYYTH3K1C4LLGXHZAbgEtBvFn4S+U8HPbDhj4nEhw==", "license": "MIT", - "peer": true, "engines": { "node": ">=20.0.0" }, @@ -723,7 +751,6 @@ "resolved": "https://registry.npmjs.org/@typespec/versioning/-/versioning-0.76.0.tgz", "integrity": "sha512-dguO/B+mwlCyenWGG+M+16cMQuGHSTJbU5Z0pyUou1uyWrB1px//s4pW7PKD14S+fPutJE0wTMQm+CctOq6quA==", "license": "MIT", - "peer": true, "engines": { "node": ">=20.0.0" }, @@ -736,7 +763,6 @@ "resolved": "https://registry.npmjs.org/@typespec/xml/-/xml-0.76.0.tgz", "integrity": "sha512-+I7hdWZDO3qBfzRT3St+1Dg/NQAMNLz8w1OydutSnVMx0G3KWg/ESonaByszBUfdq6Z5iTtls3gvj4wgrw80gA==", "license": "MIT", - "peer": true, "engines": { "node": ">=20.0.0" }, diff --git a/eng/emitter-package.json b/eng/emitter-package.json index 80e7f59d40..553e9fad6d 100644 --- a/eng/emitter-package.json +++ b/eng/emitter-package.json @@ -12,6 +12,12 @@ "@typespec/openapi": "^1.6.0", "@typespec/rest": "~0.76.0", "@typespec/versioning": "~0.76.0", - "@typespec/xml": "~0.76.0" + "@typespec/xml": "~0.76.0", + "@typespec/events": "~0.76.0", + "@typespec/sse": "~0.76.0", + "@typespec/streams": "~0.76.0", + "@azure-tools/typespec-azure-resource-manager": "~0.62.0", + "@azure-tools/typespec-autorest": "~0.62.0", + "@azure-tools/typespec-liftr-base": "0.11.0" } -} \ No newline at end of file +} diff --git a/eng/scripts/Init-TspConfigs.ps1 b/eng/scripts/Init-TspConfigs.ps1 new file mode 100644 index 0000000000..fa45e87002 --- /dev/null +++ b/eng/scripts/Init-TspConfigs.ps1 @@ -0,0 +1,529 @@ +#!/usr/bin/env pwsh + +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.SYNOPSIS + Runs 'tsp-client init' for every directory containing a tspconfig file. + +.DESCRIPTION + Searches recursively for tspconfig files (defaults to tspconfig.yaml) beneath the + provided path, filters them to those referencing the typespec-rust emitter, and + runs 'tsp-client init -c ' from the azure-sdk-for-rust repository root for + every remaining file. Successful runs also ensure each crate's service directory + is listed in the root Cargo.toml workspace members array. + +.PARAMETER Path + Root path that contains TypeSpec projects. Defaults to ~/src/azure-rest-api-specs. + +.PARAMETER ConfigNames + One or more filenames to search for. Defaults to 'tspconfig.yaml'. + +.EXAMPLE + .\Init-TspConfigs.ps1 + Runs tsp-client init for every tspconfig.yaml under ~/src/azure-rest-api-specs. + +.EXAMPLE + .\Init-TspConfigs.ps1 -Path "C:\repos\azure-rest-api-specs" -ConfigNames "tspconfig.yaml","tspconfig.yml" + Runs tsp-client init for matching files under the provided repo path. +#> + +[CmdletBinding(SupportsShouldProcess = $true)] +param( + [Parameter(Mandatory = $false)] + [string]$Path = (Join-Path $HOME "src/azure-rest-api-specs"), + + [Parameter(Mandatory = $false)] + [string[]]$ConfigNames = @("tspconfig.yaml") +) + +$script:YamlSupportChecked = $false + +function Ensure-YamlSupport { + if ($script:YamlSupportChecked) { + return + } + + $script:YamlSupportChecked = $true + + if (-not (Get-Command -Name ConvertFrom-Yaml -ErrorAction SilentlyContinue)) { + try { + Import-Module -Name Microsoft.PowerShell.Utility -ErrorAction SilentlyContinue | Out-Null + } + catch { + # ignore and try the next option + } + } + + if (-not (Get-Command -Name ConvertFrom-Yaml -ErrorAction SilentlyContinue)) { + try { + Import-Module -Name powershell-yaml -ErrorAction Stop | Out-Null + } + catch { + $installMessage = "ConvertFrom-Yaml cmdlet is unavailable. Install PowerShell 7+ or run 'Install-Module -Name powershell-yaml -Scope CurrentUser' and retry." + throw $installMessage + } + } +} + +function ConvertFrom-TspYaml { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Content, + + [Parameter(Mandatory = $true)] + [string]$SourcePath + ) + + Ensure-YamlSupport + + try { + return $Content | ConvertFrom-Yaml + } + catch { + throw "Unable to parse YAML for '$SourcePath': $_" + } +} + +function Add-CargoMembers { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$CargoPath, + + [Parameter(Mandatory = $true)] + [string[]]$MembersToAdd + ) + + if (-not $MembersToAdd -or $MembersToAdd.Count -eq 0) { + return @() + } + + if (-not (Test-Path -Path $CargoPath -PathType Leaf)) { + Write-Warning "Cargo.toml not found at $CargoPath. Skipping workspace update." + return @() + } + + $normalizedMembers = @($MembersToAdd | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Sort-Object -Unique) + if ($normalizedMembers.Count -eq 0) { + return @() + } + + $cargoLines = [System.Collections.Generic.List[string]]::new() + $cargoLines.AddRange([string[]](Get-Content -Path $CargoPath)) + + $membersStart = -1 + for ($i = 0; $i -lt $cargoLines.Count; $i++) { + if ($cargoLines[$i].TrimStart().StartsWith("members = [")) { + $membersStart = $i + break + } + } + + if ($membersStart -lt 0) { + Write-Warning "Could not locate 'members = [' block in Cargo.toml." + return + } + + $membersEnd = -1 + for ($i = $membersStart + 1; $i -lt $cargoLines.Count; $i++) { + if ($cargoLines[$i].Trim() -eq "]") { + $membersEnd = $i + break + } + } + + if ($membersEnd -lt 0) { + Write-Warning "Unterminated members array in Cargo.toml." + return + } + + $existingMembers = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::Ordinal) + for ($i = $membersStart + 1; $i -lt $membersEnd; $i++) { + $line = $cargoLines[$i].Trim() + if ($line -match '"([^\"]+)"') { + [void]$existingMembers.Add($matches[1]) + } + } + + $insertLines = @() + $addedMembers = @() + foreach ($member in $normalizedMembers) { + if (-not $existingMembers.Contains($member)) { + $insertLines += (' "' + $member + '",') + $addedMembers += $member + } + } + + if ($insertLines.Count -eq 0) { + Write-Host "No Cargo.toml updates required; all crate paths already present." -ForegroundColor Green + return @() + } + + foreach ($line in $insertLines) { + $cargoLines.Insert($membersEnd, $line) + $membersEnd++ + } + + Set-Content -Path $CargoPath -Value $cargoLines -Encoding UTF8 + + $entryWord = if ($insertLines.Count -eq 1) { "entry" } else { "entries" } + Write-Host "Added $($insertLines.Count) $entryWord to Cargo.toml members." -ForegroundColor Green + + return ,$addedMembers +} + +function Remove-CargoMembers { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$CargoPath, + + [Parameter(Mandatory = $true)] + [string[]]$MembersToRemove + ) + + if (-not $MembersToRemove -or $MembersToRemove.Count -eq 0) { + return + } + + if (-not (Test-Path -Path $CargoPath -PathType Leaf)) { + Write-Warning "Cargo.toml not found at $CargoPath. Unable to remove workspace members." + return + } + + $normalizedMembers = @($MembersToRemove | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Sort-Object -Unique) + if ($normalizedMembers.Count -eq 0) { + return + } + + $cargoLines = [System.Collections.Generic.List[string]]::new() + $cargoLines.AddRange([string[]](Get-Content -Path $CargoPath)) + + $membersStart = -1 + for ($i = 0; $i -lt $cargoLines.Count; $i++) { + if ($cargoLines[$i].TrimStart().StartsWith("members = [")) { + $membersStart = $i + break + } + } + + if ($membersStart -lt 0) { + Write-Warning "Could not locate 'members = [' block in Cargo.toml." + return + } + + $membersEnd = -1 + for ($i = $membersStart + 1; $i -lt $cargoLines.Count; $i++) { + if ($cargoLines[$i].Trim() -eq "]") { + $membersEnd = $i + break + } + } + + if ($membersEnd -lt 0) { + Write-Warning "Unterminated members array in Cargo.toml." + return + } + + $removalSet = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::Ordinal) + foreach ($member in $normalizedMembers) { + [void]$removalSet.Add($member) + } + + $removedCount = 0 + for ($i = $membersEnd - 1; $i -gt $membersStart; $i--) { + $line = $cargoLines[$i].Trim() + if ($line -match '"([^"]+)"' -and $removalSet.Contains($matches[1])) { + $cargoLines.RemoveAt($i) + $removedCount++ + } + } + + if ($removedCount -eq 0) { + return + } + + Set-Content -Path $CargoPath -Value $cargoLines -Encoding UTF8 + $entryWord = if ($removedCount -eq 1) { "entry" } else { "entries" } + Write-Host "Removed $removedCount $entryWord from Cargo.toml members after failure." -ForegroundColor Yellow +} + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +if (-not (Get-Command tsp-client -ErrorAction SilentlyContinue)) { + Write-Error "tsp-client CLI not found in PATH. Install it via 'npm install -g @azure-tools/typespec-client' or ensure it's accessible." + exit 1 +} + +try { + $resolvedPath = (Resolve-Path -Path $Path).Path +} +catch { + Write-Error "Unable to resolve path '$Path'. $_" + exit 1 +} + +$specPathCandidate = Join-Path -Path $resolvedPath -ChildPath "specification" +if (Test-Path -Path $specPathCandidate -PathType Container) { + $searchRoot = (Resolve-Path -Path $specPathCandidate).Path +} +elseif ((Split-Path -Path $resolvedPath -Leaf) -eq "specification") { + $searchRoot = $resolvedPath +} +else { + Write-Error "Could not locate a 'specification' directory under '$resolvedPath'. Provide the azure-rest-api-specs repo root or the specifications directory path." + exit 1 +} + +Write-Host "Searching for config files ($($ConfigNames -join ', ')) under: $searchRoot" -ForegroundColor Green + +$configFiles = @() +foreach ($name in $ConfigNames) { + $found = Get-ChildItem -Path $searchRoot -Recurse -File -Filter $name -ErrorAction SilentlyContinue + if ($found) { + $configFiles += $found + } +} + +$configFiles = $configFiles | Sort-Object -Property FullName -Unique + +if (-not $configFiles -or $configFiles.Count -eq 0) { + Write-Host "No matching tspconfig files found." -ForegroundColor Yellow + exit 0 +} + +Write-Host "Found $($configFiles.Count) config file(s)." -ForegroundColor Green + +$configFiles = $configFiles | Where-Object { + try { + Select-String -Path $_.FullName -Pattern "typespec-rust" -SimpleMatch -Quiet + } + catch { + Write-Warning "Unable to inspect file '$($_.FullName)': $_" + $false + } +} + +if (-not $configFiles -or $configFiles.Count -eq 0) { + Write-Host "No tspconfig files referencing the typespec-rust emitter were found." -ForegroundColor Yellow + exit 0 +} + +Write-Host "Filtered to $($configFiles.Count) config file(s) referencing typespec-rust." -ForegroundColor Green + +$configEntries = @() +foreach ($file in $configFiles) { + try { + $rawContent = Get-Content -Path $file.FullName -Raw + } + catch { + Write-Warning "Unable to read '$($file.FullName)': $_" + continue + } + + try { + $configData = ConvertFrom-TspYaml -Content $rawContent -SourcePath $file.FullName + } + catch { + Write-Warning $_ + continue + } + + if ($configData -is [System.Array]) { + if ($configData.Count -gt 0) { + $configData = $configData[0] + } + else { + Write-Warning "Empty YAML document in '$($file.FullName)'. Skipping." + continue + } + } + + $emitterConfig = $configData.options.'@azure-tools/typespec-rust' + if (-not $emitterConfig) { + Write-Warning "typespec-rust emitter configuration not found in '$($file.FullName)'. Skipping." + continue + } + + $crateName = $emitterConfig.'crate-name' + if ([string]::IsNullOrWhiteSpace($crateName)) { + Write-Warning "crate-name missing in '$($file.FullName)'. Skipping." + continue + } + + $parametersNode = $configData.parameters + if (-not $parametersNode) { + Write-Warning "parameters section missing in '$($file.FullName)'. Skipping." + continue + } + + $serviceDirRaw = $parametersNode.'service-dir' + $serviceDir = $null + + if ($serviceDirRaw -is [string]) { + $serviceDir = $serviceDirRaw + } + elseif ($serviceDirRaw -is [System.Collections.IDictionary]) { + foreach ($key in @('value', 'default', 'path', 'dir')) { + if ($serviceDirRaw.Contains($key) -and -not [string]::IsNullOrWhiteSpace($serviceDirRaw[$key])) { + $serviceDir = [string]$serviceDirRaw[$key] + break + } + } + } + elseif ($serviceDirRaw -is [System.Collections.IEnumerable]) { + foreach ($candidate in $serviceDirRaw) { + if ($candidate -is [string] -and -not [string]::IsNullOrWhiteSpace($candidate)) { + $serviceDir = $candidate + break + } + } + } + + if ([string]::IsNullOrWhiteSpace($serviceDir)) { + Write-Warning "service-dir missing in '$($file.FullName)'. Skipping." + continue + } + + $normalizedServiceDir = ($serviceDir -replace '\\', '/').Trim() + $normalizedServiceDir = $normalizedServiceDir -replace '^[./]+', '' + + if ([string]::IsNullOrWhiteSpace($normalizedServiceDir)) { + Write-Warning "Unable to normalize service-dir value in '$($file.FullName)'. Skipping." + continue + } + + $crateRelativePath = Join-Path -Path $normalizedServiceDir -ChildPath $crateName + $crateRelativePath = ($crateRelativePath -replace '\\', '/').Trim() + $crateRelativePath = $crateRelativePath -replace '^[./]+', '' + + if ([string]::IsNullOrWhiteSpace($crateRelativePath)) { + Write-Warning "Unable to compute crate path for '$($file.FullName)'. Skipping." + continue + } + + $configEntries += [PSCustomObject]@{ + File = $file.FullName + CrateName = $crateName + ServiceDir = $normalizedServiceDir + CratePath = $crateRelativePath + } +} + +if (-not $configEntries -or $configEntries.Count -eq 0) { + Write-Host "No tspconfig files referencing typespec-rust with crate metadata were found." -ForegroundColor Yellow + exit 0 +} + +Write-Host "Loaded emitter metadata for $($configEntries.Count) config file(s)." -ForegroundColor Green + +$successes = @() +$failures = @() +$warnings = @() + +try { + $repoRoot = (Resolve-Path -Path (Join-Path $PSScriptRoot "../..")).Path +} +catch { + Write-Error "Unable to resolve the azure-sdk-for-rust repository root relative to this script. $_" + exit 1 +} + +$cargoPath = Join-Path -Path $repoRoot -ChildPath "Cargo.toml" + +Write-Host "Running tsp-client init commands from repository root: $repoRoot" -ForegroundColor Green + +$locationPushed = $false +try { + Push-Location $repoRoot + $locationPushed = $true + + foreach ($config in $configEntries) { + $target = $config.File + $action = "Run tsp-client init for crate $($config.CrateName)" + + if (-not $PSCmdlet.ShouldProcess($target, $action)) { + continue + } + + $addedMembers = @(Add-CargoMembers -CargoPath $cargoPath -MembersToAdd @($config.CratePath)) + + $fullServicePath = Join-Path -Path $repoRoot -ChildPath $config.CratePath + if (-not (Test-Path -Path $fullServicePath)) { + Write-Warning "Crate directory '$($config.CratePath)' does not currently exist under the SDK repository." + } + + Write-Host "\nRunning tsp-client init for crate '$($config.CrateName)' using config: $($config.File)" -ForegroundColor Cyan + + $exitCode = $null + try { + npm exec --prefix eng/common/tsp-client/ -- tsp-client init -c $config.File + $exitCode = $LASTEXITCODE + } + catch { + $failureMessage = "Error running tsp-client init for crate '$($config.CrateName)' (config: $($config.File)) - $_" + Write-Error $failureMessage + $failures += $failureMessage + if ($addedMembers.Count -gt 0) { + Remove-CargoMembers -CargoPath $cargoPath -MembersToRemove $addedMembers + } + continue + } + + if ($exitCode -ne 0) { + $failureMessage = "tsp-client init failed for crate '$($config.CrateName)' (config: $($config.File)) (exit code: $exitCode)" + Write-Warning $failureMessage + $failures += $failureMessage + $warnings += $failureMessage + if ($addedMembers.Count -gt 0) { + Remove-CargoMembers -CargoPath $cargoPath -MembersToRemove $addedMembers + } + } + else { + $successMessage = "$($config.CrateName) [$($config.CratePath)]" + Write-Host "Successfully initialized: $successMessage" -ForegroundColor Green + $successes += $successMessage + } + } +} +finally { + if ($locationPushed) { + Pop-Location + } +} + +Write-Host "\nClient generation summary:" -ForegroundColor Green +Write-Host " Successful ($($successes.Count)):" -ForegroundColor Green +if ($successes.Count -gt 0) { + $successes | ForEach-Object { Write-Host " $_" } +} +else { + Write-Host " None" +} + +Write-Host " Failed ($($failures.Count)):" -ForegroundColor Yellow +if ($failures.Count -gt 0) { + $failures | ForEach-Object { Write-Host " $_" } +} +else { + Write-Host " None" +} + +Write-Host " Warnings ($($warnings.Count)):" -ForegroundColor Yellow +if ($warnings.Count -gt 0) { + $warnings | ForEach-Object { Write-Host " $_" } +} +else { + Write-Host " None" +} + +if ($failures.Count -gt 0) { + Write-Host "\nCompleted initializing TypeSpec configs with failures." -ForegroundColor Yellow + exit 1 +} + +Write-Host "\nCompleted initializing all TypeSpec configs." -ForegroundColor Green