diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d15d31e..731bd13 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -74,9 +74,9 @@ jobs:
run: |
$ErrorActionPreference = 'Stop'
$script = Join-Path $pwd 'src/Azure.Cli/unix-exec.ps1'
- $azure = Get-ChildItem bin -Filter "Azure.Cli.linux-x64.*.nupkg" | Where-Object Name -notlike '*.symbols.*' | Select-Object -First 1
+ $azure = Get-ChildItem bin -Filter "azx.cli.linux-x64.*.nupkg" | Where-Object Name -notlike '*.symbols.*' | Select-Object -First 1
$azx = Get-ChildItem bin -Filter "azx.linux-x64.*.nupkg" | Where-Object Name -notlike '*.symbols.*' | Select-Object -First 1
- if (-not $azure) { throw "Azure.Cli.linux-x64 nupkg not found" }
+ if (-not $azure) { throw "azx.cli.linux-x64 nupkg not found" }
if (-not $azx) { throw "azx.linux-x64 nupkg not found" }
foreach ($pkg in @($azure, $azx)) {
& pwsh -NoProfile -File $script -Nupkg $pkg.FullName -Assert
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index b6c6bd5..f2d9057 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -70,9 +70,9 @@ jobs:
run: |
$ErrorActionPreference = 'Stop'
$script = Join-Path $pwd 'src/Azure.Cli/unix-exec.ps1'
- $azure = Get-ChildItem bin -Filter "Azure.Cli.${{ matrix.rid }}.*.nupkg" | Where-Object Name -notlike '*.symbols.*' | Select-Object -First 1
+ $azure = Get-ChildItem bin -Filter "azx.cli.${{ matrix.rid }}.*.nupkg" | Where-Object Name -notlike '*.symbols.*' | Select-Object -First 1
$azx = Get-ChildItem bin -Filter "azx.${{ matrix.rid }}.*.nupkg" | Where-Object Name -notlike '*.symbols.*' | Select-Object -First 1
- if (-not $azure) { throw "Azure.Cli.${{ matrix.rid }} nupkg not found" }
+ if (-not $azure) { throw "azx.cli.${{ matrix.rid }} nupkg not found" }
if (-not $azx) { throw "azx.${{ matrix.rid }} nupkg not found" }
foreach ($pkg in @($azure, $azx)) {
& pwsh -NoProfile -File $script -Nupkg $pkg.FullName -Assert
@@ -84,9 +84,9 @@ jobs:
with:
name: package-${{ matrix.rid }}
# Only RID nupkgs. Pointer nupkgs are produced on every OS; merging them
- # into one Azure.Cli.{version}.nupkg corrupts the zip (NUGET 400 / sleet EOCD).
+ # into one azx.cli.{version}.nupkg corrupts the zip (NUGET 400 / sleet EOCD).
path: |
- bin/Azure.Cli.${{ matrix.rid }}.*.nupkg
+ bin/azx.cli.${{ matrix.rid }}.*.nupkg
bin/azx.${{ matrix.rid }}.*.nupkg
retention-days: 30
if-no-files-found: error
@@ -97,7 +97,7 @@ jobs:
with:
name: package-pointer
path: |
- bin/Azure.Cli.${{ env.Version }}.nupkg
+ bin/azx.cli.${{ env.Version }}.nupkg
bin/azx.${{ env.Version }}.nupkg
retention-days: 30
if-no-files-found: error
@@ -161,12 +161,12 @@ jobs:
}
$names = Get-ChildItem -File -Filter *.nupkg | ForEach-Object Name
foreach ($need in @(
- "Azure.Cli.$env:Version.nupkg",
- "Azure.Cli.win-x64.$env:Version.nupkg",
- "Azure.Cli.linux-x64.$env:Version.nupkg",
- "Azure.Cli.linux-arm64.$env:Version.nupkg",
- "Azure.Cli.osx-x64.$env:Version.nupkg",
- "Azure.Cli.osx-arm64.$env:Version.nupkg",
+ "azx.cli.$env:Version.nupkg",
+ "azx.cli.win-x64.$env:Version.nupkg",
+ "azx.cli.linux-x64.$env:Version.nupkg",
+ "azx.cli.linux-arm64.$env:Version.nupkg",
+ "azx.cli.osx-x64.$env:Version.nupkg",
+ "azx.cli.osx-arm64.$env:Version.nupkg",
"azx.$env:Version.nupkg",
"azx.win-x64.$env:Version.nupkg",
"azx.linux-x64.$env:Version.nupkg",
diff --git a/CONTEXT.md b/CONTEXT.md
index e8938d4..fe25f84 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -5,11 +5,11 @@ NuGet pointer and per-RID payloads that put a working Azure CLI next to a .NET a
## Language
**Pointer**:
-The `Azure.Cli` nupkg: `Cli` plus `runtime.json` mapping each supported RID to a RID package.
+The `azx.cli` nupkg: `Cli` plus `runtime.json` mapping each supported RID to a RID package.
_Avoid_: metapackage, tool package, native package
**RID package**:
-`Azure.Cli.{rid}` — one nupkg, one RID, one Payload.
+`azx.cli.{rid}` — one nupkg, one RID, one Payload.
_Avoid_: native package, runtime pack, sidecar package
**Payload**:
diff --git a/docs/adr/0002-nupkg-version-is-upstream.md b/docs/adr/0002-nupkg-version-is-upstream.md
index 64cffb4..b4128f3 100644
--- a/docs/adr/0002-nupkg-version-is-upstream.md
+++ b/docs/adr/0002-nupkg-version-is-upstream.md
@@ -1,3 +1,3 @@
# Nupkg version is Upstream
-`Azure.Cli` and `azx` use the Azure CLI version (`2.89.1`) so `ndnx azx@2.89.1` is that CLI. The pin is `azure-cli.version`. CI dogfood stays `42.42.*`. Packaging-only republishes of the same CLI use a SemVer label, not a different major.
+`azx.cli` and `azx` use the Azure CLI version (`2.89.1`) so `ndnx azx@2.89.1` is that CLI. The pin is `azure-cli.version`. CI dogfood stays `42.42.*`. Packaging-only republishes of the same CLI use a SemVer label, not a different major.
diff --git a/readme.md b/readme.md
index 90d5843..7e86f17 100644
--- a/readme.md
+++ b/readme.md
@@ -32,7 +32,7 @@ dotnet tool install -g azx #once
azx --version # azx version, then az --version
```
-`PackageReference` `Azure.Cli` and pack/publish for your RID to copy `az/` next to the app. `Azure.Cli.ResolvePath()` is the Payload `az` executable.
+`PackageReference` `azx.cli` and pack/publish for your RID to copy `az/` next to the app. `Azure.Cli.ResolvePath()` is the Payload `az` executable.
---
diff --git a/src/Azure.Cli/Azure.Cli.csproj b/src/Azure.Cli/Azure.Cli.csproj
index c51c963..8a3a44b 100644
--- a/src/Azure.Cli/Azure.Cli.csproj
+++ b/src/Azure.Cli/Azure.Cli.csproj
@@ -1,10 +1,10 @@
net10.0
- Azure.Cli
+ azx.cli
Azure
Azure.Cli
- Azure CLI payload for .NET: PackageReference Azure.Cli and publish/pack for your RID to get az/ plus Azure.Cli.ResolvePath.
+ Azure CLI payload for .NET: PackageReference azx.cli and publish/pack for your RID to get az/ plus Azure.Cli.ResolvePath.
true
true
win-x64;linux-x64;linux-arm64;osx-x64;osx-arm64
@@ -30,10 +30,10 @@
-
+
-
-
+
+
diff --git a/src/Azure.Cli/Cli.cs b/src/Azure.Cli/Cli.cs
index 2086660..7e04c3c 100644
--- a/src/Azure.Cli/Cli.cs
+++ b/src/Azure.Cli/Cli.cs
@@ -17,7 +17,7 @@ public static string ResolvePath(string? baseDirectory = null)
if (!File.Exists(path))
{
throw new FileNotFoundException(
- $"Azure CLI payload was not found at '{path}'. PackageReference Azure.Cli and publish/pack for your RID.",
+ $"Azure CLI payload was not found at '{path}'. PackageReference azx.cli and publish/pack for your RID.",
path);
}
diff --git a/src/Azure.Cli/build/Azure.Cli.targets b/src/Azure.Cli/build/Azure.Cli.targets
deleted file mode 100644
index 69908a6..0000000
--- a/src/Azure.Cli/build/Azure.Cli.targets
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/Azure.Cli/build/azx.cli.targets b/src/Azure.Cli/build/azx.cli.targets
new file mode 100644
index 0000000..582cb1e
--- /dev/null
+++ b/src/Azure.Cli/build/azx.cli.targets
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/Azure.Cli/buildTransitive/Azure.Cli.targets b/src/Azure.Cli/buildTransitive/azx.cli.targets
similarity index 94%
rename from src/Azure.Cli/buildTransitive/Azure.Cli.targets
rename to src/Azure.Cli/buildTransitive/azx.cli.targets
index 8c0cc4a..0c022a2 100644
--- a/src/Azure.Cli/buildTransitive/Azure.Cli.targets
+++ b/src/Azure.Cli/buildTransitive/azx.cli.targets
@@ -14,7 +14,7 @@
<_AzureCliVersion>$([System.IO.Path]::GetFileName($([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..'))))
- <_AzureCliPackageFolder>$([System.String]::Copy('azure.cli.$(_AzureCliRid)').ToLowerInvariant())
+ <_AzureCliPackageFolder>$([System.String]::Copy('$(MSBuildThisFileName).$(_AzureCliRid)').ToLowerInvariant())
$(NuGetPackageRoot)$(_AzureCliPackageFolder)\$(_AzureCliVersion)\az
diff --git a/src/Azure.Cli/payload.functions.ps1 b/src/Azure.Cli/payload.functions.ps1
index df8d321..54162ef 100644
--- a/src/Azure.Cli/payload.functions.ps1
+++ b/src/Azure.Cli/payload.functions.ps1
@@ -1,17 +1,64 @@
#Requires -Version 7
# Download/extract helpers for payload.ps1. Get-SourcesRoot is the sources unpack unit.
+function Get-GitHubAuthTokens {
+ # In GitHub Actions prefer GITHUB_TOKEN: per-repo quota, unlike a shared GH_TOKEN PAT.
+ $tokens = [System.Collections.Generic.List[string]]::new()
+ $order = if ($env:GITHUB_ACTIONS -eq 'true') {
+ @($env:GITHUB_TOKEN, $env:GH_TOKEN)
+ }
+ else {
+ @($env:GH_TOKEN, $env:GITHUB_TOKEN)
+ }
+ foreach ($t in $order) {
+ if (-not [string]::IsNullOrWhiteSpace($t) -and -not $tokens.Contains($t)) {
+ $tokens.Add($t)
+ }
+ }
+ return $tokens
+}
+
function Get-GitHubHeaders {
$headers = @{ 'User-Agent' = 'azx-payload' }
- if ($env:GH_TOKEN) {
- $headers['Authorization'] = "Bearer $($env:GH_TOKEN)"
- }
- elseif ($env:GITHUB_TOKEN) {
- $headers['Authorization'] = "Bearer $($env:GITHUB_TOKEN)"
+ $token = @(Get-GitHubAuthTokens) | Select-Object -First 1
+ if ($token) {
+ $headers['Authorization'] = "Bearer $token"
}
return $headers
}
+function Invoke-GitHubRestMethod([string] $Uri) {
+ $attempts = [System.Collections.Generic.List[string]]::new()
+ foreach ($t in @(Get-GitHubAuthTokens)) {
+ $attempts.Add($t)
+ }
+ $attempts.Add('') # unauthenticated last; public REST still has an IP quota
+
+ $errors = [System.Collections.Generic.List[string]]::new()
+ foreach ($token in $attempts) {
+ $headers = @{ 'User-Agent' = 'azx-payload' }
+ if ($token) {
+ $headers['Authorization'] = "Bearer $token"
+ }
+ try {
+ return Invoke-RestMethod -Uri $Uri -Headers $headers
+ }
+ catch {
+ $status = $null
+ try { $status = [int]$_.Exception.Response.StatusCode } catch { }
+ $msg = [string]$_
+ $errors.Add($msg)
+ $retry = ($status -in 401, 403, 429) -or ($msg -match 'rate limit') -or ($msg -match 'Bad credentials')
+ if ($retry) {
+ Write-Host "GitHub API rejected request$(if ($null -ne $status) { " ($status)" }); trying next credentials"
+ continue
+ }
+ throw
+ }
+ }
+ throw "GitHub API failed for $Uri. $($errors -join ' | ')"
+}
+
function Save-Url([string] $Url, [string] $Dest) {
if (Test-Path $Dest) {
Write-Host "Cached $Dest"
diff --git a/src/Azure.Cli/payload.ps1 b/src/Azure.Cli/payload.ps1
index 8f58b22..97c8ed4 100644
--- a/src/Azure.Cli/payload.ps1
+++ b/src/Azure.Cli/payload.ps1
@@ -60,12 +60,42 @@ function Get-PbsTriple([string] $PayloadRid) {
}
function Get-PbsAsset([string] $PythonVersion, [string] $Triple) {
- $releases = Invoke-RestMethod -Uri 'https://api.github.com/repos/astral-sh/python-build-standalone/releases?per_page=15' -Headers (Get-GitHubHeaders)
$pattern = "^cpython-$([regex]::Escape($PythonVersion))\+.*-$([regex]::Escape($Triple))-install_only_stripped\.tar\.gz$"
- foreach ($release in $releases) {
- $asset = @($release.assets | Where-Object { $_.name -match $pattern }) | Select-Object -First 1
- if ($asset) {
- return $asset
+ try {
+ $releases = Invoke-GitHubRestMethod 'https://api.github.com/repos/astral-sh/python-build-standalone/releases?per_page=15'
+ foreach ($release in @($releases)) {
+ if (-not $release.assets) { continue }
+ $asset = @($release.assets | Where-Object { $_.name -match $pattern }) | Select-Object -First 1
+ if ($asset) {
+ return $asset
+ }
+ }
+ Write-Host 'No PBS asset in GitHub API releases; trying releases.atom'
+ }
+ catch {
+ Write-Host "PBS GitHub API lookup failed: $_"
+ }
+
+ # github.com HTML/atom does not consume REST API quota.
+ $atomHeaders = @{ 'User-Agent' = 'azx-payload' }
+ $atom = Invoke-WebRequest -Uri 'https://github.com/astral-sh/python-build-standalone/releases.atom' -Headers $atomHeaders
+ $tags = [regex]::Matches($atom.Content, 'python-build-standalone/releases/tag/([^<"\s]+)') |
+ ForEach-Object { [System.Uri]::UnescapeDataString($_.Groups[1].Value) } |
+ Select-Object -Unique
+ foreach ($tag in $tags) {
+ $name = "cpython-$PythonVersion+$tag-$Triple-install_only_stripped.tar.gz"
+ $url = "https://github.com/astral-sh/python-build-standalone/releases/download/$tag/$name"
+ try {
+ $head = Invoke-WebRequest -Uri $url -Method Head -SkipHttpErrorCheck -Headers $atomHeaders
+ if ($head.StatusCode -ge 200 -and $head.StatusCode -lt 400) {
+ return [pscustomobject]@{
+ name = $name
+ browser_download_url = $url
+ }
+ }
+ }
+ catch {
+ # Tag does not contain this CPython build, or HEAD was not supported.
}
}
throw "No python-build-standalone install_only_stripped asset for CPython $PythonVersion on $Triple."
diff --git a/src/Tests/PackTests.cs b/src/Tests/PackTests.cs
index f5aa5ff..f01d65c 100644
--- a/src/Tests/PackTests.cs
+++ b/src/Tests/PackTests.cs
@@ -23,12 +23,15 @@ public void Pointer_and_rid_csproj_use_pack_split()
Assert.Contains("src/azx/azx.csproj", slnx);
var azureCli = File.ReadAllText(Path.Combine(repo, "src", "Azure.Cli", "Azure.Cli.csproj"));
- Assert.Contains("Azure.Cli", azureCli);
+ Assert.Contains("azx.cli", azureCli);
+ Assert.Contains("Azure.Cli", azureCli);
+ Assert.Contains("Azure", azureCli);
Assert.Contains("win-x64;linux-x64;linux-arm64;osx-x64;osx-arm64", azureCli);
Assert.DoesNotContain("NuGetizer", azureCli, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain("az\", consumer.Replace('/', '\\'));
Assert.DoesNotContain("runtimes/$(RuntimeIdentifier)/native/", consumer);
@@ -72,13 +75,15 @@ public void Pointer_and_rid_csproj_use_pack_split()
Assert.Contains("true", azx);
Assert.Contains("azx", azx);
Assert.Contains("win-x64;linux-x64;linux-arm64;osx-x64;osx-arm64", azx);
- Assert.Contains("""""", azx);
+ Assert.Contains("""""", azx);
Assert.DoesNotContain("NuGetizer", azx, StringComparison.OrdinalIgnoreCase);
Assert.Contains("Readme", azx);
var nuget = File.ReadAllText(Path.Combine(repo, "src", "azx", "nuget.config"));
Assert.Contains("key=\"local\"", nuget);
Assert.Contains("../../bin", nuget);
- Assert.Contains("Azure.Cli", nuget);
+ Assert.Contains("pattern=\"azx.cli\"", nuget);
+ Assert.Contains("pattern=\"azx.cli.*\"", nuget);
+ Assert.DoesNotContain("Azure.Cli", nuget);
Assert.DoesNotContain("win-arm64", azx);
Assert.DoesNotContain("azx.$(RuntimeIdentifier)", azx);
@@ -131,8 +136,8 @@ public void WriteAzureCliRuntimeJson_maps_five_rids()
{
var range = runtimes
.GetProperty(rid)
- .GetProperty("Azure.Cli")
- .GetProperty("Azure.Cli." + rid)
+ .GetProperty("azx.cli")
+ .GetProperty("azx.cli." + rid)
.GetString();
Assert.False(string.IsNullOrWhiteSpace(range));
Assert.StartsWith("[", range);
@@ -149,7 +154,7 @@ public void Packed_nupkgs_have_pointer_and_rid_layout()
if (!Directory.Exists(bin))
return;
- var nupkgs = Directory.GetFiles(bin, "Azure.Cli*.nupkg")
+ var nupkgs = Directory.GetFiles(bin, "azx.cli*.nupkg")
.Where(f => !f.Contains(".symbols.", StringComparison.OrdinalIgnoreCase))
.ToArray();
if (nupkgs.Length == 0)
@@ -162,7 +167,7 @@ public void Packed_nupkgs_have_pointer_and_rid_layout()
var names = ZipNames(pointer);
Assert.Contains(names, n => n == "runtime.json" || n == "runtime.json/");
Assert.Contains(names, n => n.Replace('\\', '/').StartsWith("lib/", StringComparison.Ordinal));
- Assert.Contains(names, n => n.Replace('\\', '/').Contains("buildTransitive/Azure.Cli.targets", StringComparison.Ordinal));
+ Assert.Contains(names, n => n.Replace('\\', '/').Contains("buildTransitive/azx.cli.targets", StringComparison.Ordinal));
Assert.DoesNotContain(names, n => n.Replace('\\', '/').StartsWith("az/", StringComparison.Ordinal));
}
@@ -276,12 +281,38 @@ public void Payload_sources_use_github_tarball_not_zip_via_tar()
Assert.DoesNotContain("tar -xf $zip", functions);
Assert.Contains("githubusercontent", functions);
Assert.DoesNotContain("Invoke-WebRequest -Uri $Url -OutFile $Dest -Headers (Get-GitHubHeaders)", functions);
+ Assert.Contains("function Get-GitHubAuthTokens", functions);
+ Assert.Contains("function Invoke-GitHubRestMethod", functions);
+ Assert.Contains("rate limit", functions);
var payload = File.ReadAllText(Path.Combine(repo, "src", "Azure.Cli", "payload.ps1"));
Assert.Contains("Expand-Zip $zip $OutDir", payload);
Assert.DoesNotContain("tar -xf $zip", payload);
Assert.Contains("Remove-UnusedPythonShare", payload);
Assert.Contains("Install-CliFromPyPI", payload);
+ Assert.Contains("Invoke-GitHubRestMethod", payload);
+ Assert.Contains("releases.atom", payload);
+ }
+
+ [Fact]
+ public void GitHubHeaders_prefer_actions_token_when_github_actions()
+ {
+ var repo = FindRepoRoot();
+ var functions = Path.Combine(repo, "src", "Azure.Cli", "payload.functions.ps1");
+ var functionsLit = functions.Replace("'", "''", StringComparison.Ordinal);
+ RunPwsh(repo, $$"""
+ $ErrorActionPreference = 'Stop'
+ Set-StrictMode -Version Latest
+ . '{{functionsLit}}'
+ $env:GITHUB_ACTIONS = 'true'
+ $env:GITHUB_TOKEN = 'actions-token'
+ $env:GH_TOKEN = 'pat-token'
+ $h = Get-GitHubHeaders
+ if ($h['Authorization'] -ne 'Bearer actions-token') { throw "Actions: $($h['Authorization'])" }
+ $env:GITHUB_ACTIONS = 'false'
+ $h = Get-GitHubHeaders
+ if ($h['Authorization'] -ne 'Bearer pat-token') { throw "Local: $($h['Authorization'])" }
+ """);
}
[Fact]
diff --git a/src/Tests/ReleaseTests.cs b/src/Tests/ReleaseTests.cs
index 84c8198..034c16d 100644
--- a/src/Tests/ReleaseTests.cs
+++ b/src/Tests/ReleaseTests.cs
@@ -50,7 +50,9 @@ public void Publish_version_does_not_double_append_preview()
Assert.DoesNotContain("Expand-Archive", publish);
Assert.DoesNotContain("tar -xf $nupkg", publish);
Assert.Contains("package-pointer", publish);
- Assert.Contains("bin/Azure.Cli.${{ matrix.rid }}.*.nupkg", publish);
+ Assert.Contains("bin/azx.cli.${{ matrix.rid }}.*.nupkg", publish);
+ Assert.Contains("bin/azx.cli.${{ env.Version }}.nupkg", publish);
+ Assert.Contains("azx.cli.$env:Version.nupkg", publish);
}
static string FindRepoRoot()
diff --git a/src/azx/azx.csproj b/src/azx/azx.csproj
index 23db7b2..cf2242f 100644
--- a/src/azx/azx.csproj
+++ b/src/azx/azx.csproj
@@ -19,12 +19,12 @@
-
+
diff --git a/src/azx/nuget.config b/src/azx/nuget.config
index a1042d4..43ebfaf 100644
--- a/src/azx/nuget.config
+++ b/src/azx/nuget.config
@@ -17,8 +17,8 @@
-
-
+
+