From 81a9c72b6657da65f13ada848c6734fdff7a4aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Wed, 18 Mar 2026 14:02:42 +0100 Subject: [PATCH 01/30] Added github workflows. --- .github/nuget.github.config | 7 + .github/workflows/ci.yml | 84 ++++ .github/workflows/package-unsigned.yml | 177 ++++++++ .pipelines/CosmosDB-Shell-Official.yml | 86 +++- .pipelines/CosmosDB-Shell-PullRequest.yml | 473 ---------------------- CONTRIBUTING.md | 5 +- README.md | 11 + 7 files changed, 358 insertions(+), 485 deletions(-) create mode 100644 .github/nuget.github.config create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/package-unsigned.yml delete mode 100644 .pipelines/CosmosDB-Shell-PullRequest.yml diff --git a/.github/nuget.github.config b/.github/nuget.github.config new file mode 100644 index 0000000..765346e --- /dev/null +++ b/.github/nuget.github.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d224759 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,84 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + workflow_dispatch: + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + BUILD_CONFIGURATION: Release + +jobs: + build-test: + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + cache: true + cache-dependency-path: | + global.json + Directory.Packages.props + **/*.csproj + + - name: Restore + run: dotnet restore CosmosDBShell.sln --configfile .github/nuget.github.config + + - name: Build solution + run: dotnet build CosmosDBShell.sln --configuration $env:BUILD_CONFIGURATION --no-restore + shell: pwsh + + - name: Test solution + run: >- + dotnet test CosmosDBShell.sln + --configuration $env:BUILD_CONFIGURATION + --no-build + --no-restore + --logger "trx;LogFileName=test-results.trx" + --results-directory TestResults + --collect "Code coverage" + shell: pwsh + + - name: Run fuzzer smoke test + working-directory: CosmosDBShell.Fuzzer + run: dotnet run --configuration $env:BUILD_CONFIGURATION --no-build --no-restore -- --all + shell: pwsh + + - name: Fail if fuzzer crash findings exist + shell: pwsh + run: | + $crashes = Get-ChildItem -Path CosmosDBShell.Fuzzer/findings -Filter 'crash_*.txt' -ErrorAction SilentlyContinue + if ($crashes -and $crashes.Count -gt 0) { + Write-Error "Fuzzer recorded $($crashes.Count) crash(es). See the 'fuzz-findings' artifact for details." + exit 1 + } + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: TestResults + if-no-files-found: ignore + + - name: Upload fuzzer findings + if: always() + uses: actions/upload-artifact@v4 + with: + name: fuzz-findings + path: CosmosDBShell.Fuzzer/findings + if-no-files-found: ignore diff --git a/.github/workflows/package-unsigned.yml b/.github/workflows/package-unsigned.yml new file mode 100644 index 0000000..73feffd --- /dev/null +++ b/.github/workflows/package-unsigned.yml @@ -0,0 +1,177 @@ +name: Package Unsigned Artifacts + +on: + workflow_dispatch: + push: + tags: + - v* + +concurrency: + group: package-unsigned-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: read + +env: + BUILD_CONFIGURATION: Release + +jobs: + package: + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + cache: true + cache-dependency-path: | + global.json + Directory.Packages.props + **/*.csproj + + - name: Compute version properties + id: version + shell: pwsh + run: | + $runNumber = [int]"${{ github.run_number }}" + $version = "1.0.$runNumber" + + if ("${{ github.ref_type }}" -eq "tag") { + $candidateVersion = "${{ github.ref_name }}" + if ($candidateVersion.StartsWith('v')) { + $candidateVersion = $candidateVersion.Substring(1) + } + + # Accept numeric SemVer: major.minor.patch with optional prerelease/build metadata + if ($candidateVersion -match '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$') { + $version = $candidateVersion + } + } + + # Derive a strictly numeric file version (major.minor.build.revision) + $numericVersion = $version.Split('+')[0].Split('-')[0] + $parts = $numericVersion.Split('.') + + $allNumeric = $true + foreach ($p in $parts) { + $tmp = 0 + if (-not [int]::TryParse($p, [ref]$tmp)) { + $allNumeric = $false + break + } + } + + if (-not $allNumeric -or $parts.Count -lt 1) { + $fileVersion = "1.0.$runNumber.0" + } else { + $major = [int]$parts[0] + $minor = if ($parts.Count -ge 2) { [int]$parts[1] } else { 0 } + $build = if ($parts.Count -ge 3) { [int]$parts[2] } else { $runNumber } + $revision = if ($parts.Count -ge 4) { [int]$parts[3] } else { 0 } + $fileVersion = "$major.$minor.$build.$revision" + } + + $infoVersion = "$version+${{ github.sha }}" + + "package_version=$version" >> $env:GITHUB_OUTPUT + "file_version=$fileVersion" >> $env:GITHUB_OUTPUT + "informational_version=$infoVersion" >> $env:GITHUB_OUTPUT + + - name: Restore + run: dotnet restore CosmosDBShell.sln --configfile .github/nuget.github.config + + - name: Build solution + run: dotnet build CosmosDBShell.sln --configuration $env:BUILD_CONFIGURATION --no-restore + shell: pwsh + + - name: Test solution + run: >- + dotnet test CosmosDBShell.sln + --configuration $env:BUILD_CONFIGURATION + --no-build + --no-restore + --logger "trx;LogFileName=test-results.trx" + --results-directory TestResults + --collect "Code coverage" + shell: pwsh + + - name: Publish runtime artifacts + shell: pwsh + run: | + $rids = @('win-x64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64') + foreach ($rid in $rids) { + dotnet publish CosmosDBShell/CosmosDBShell.csproj ` + --configuration $env:BUILD_CONFIGURATION ` + --no-restore ` + -r $rid ` + --output "out/$rid" ` + /p:Version=${{ steps.version.outputs.package_version }} ` + /p:FileVersion=${{ steps.version.outputs.file_version }} ` + /p:InformationalVersion=${{ steps.version.outputs.informational_version }} + } + + - name: Pack unsigned NuGet artifacts + shell: pwsh + run: | + New-Item -ItemType Directory -Path out/nupkg -Force | Out-Null + dotnet pack CosmosDBShell/CosmosDBShell.csproj ` + --configuration $env:BUILD_CONFIGURATION ` + --no-build ` + --no-restore ` + --output out/nupkg ` + /p:PackageVersion=${{ steps.version.outputs.package_version }} ` + /p:Version=${{ steps.version.outputs.package_version }} ` + /p:FileVersion=${{ steps.version.outputs.file_version }} ` + /p:InformationalVersion=${{ steps.version.outputs.informational_version }} ` + /p:ContinuousIntegrationBuild=true + + - name: Validate NuGet package set + shell: pwsh + run: | + $pkgDir = Join-Path $pwd 'out/nupkg' + $ridPatterns = @( + 'CosmosDBShell.win-x64.*.nupkg', + 'CosmosDBShell.linux-x64.*.nupkg', + 'CosmosDBShell.linux-arm64.*.nupkg', + 'CosmosDBShell.osx-x64.*.nupkg', + 'CosmosDBShell.osx-arm64.*.nupkg' + ) + + foreach ($pattern in $ridPatterns) { + $matches = Get-ChildItem -Path (Join-Path $pkgDir $pattern) -ErrorAction SilentlyContinue + if (-not $matches -or $matches.Count -eq 0) { + Write-Error "Expected package was not generated: $pattern" + exit 1 + } + } + + $allPackages = Get-ChildItem -Path (Join-Path $pkgDir 'CosmosDBShell.*.nupkg') -ErrorAction SilentlyContinue + $pointerPackages = $allPackages | Where-Object { + $_.Name -notmatch '^CosmosDBShell\.(win-x64|linux-x64|linux-arm64|osx-x64|osx-arm64)\..+\.nupkg$' + } + + if (-not $pointerPackages -or $pointerPackages.Count -ne 1) { + $names = @($pointerPackages | ForEach-Object { $_.Name }) + Write-Error "Expected exactly one pointer package (non-RID). Found: $($names -join ', ')" + exit 1 + } + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: TestResults + if-no-files-found: ignore + + - name: Upload unsigned artifacts + uses: actions/upload-artifact@v4 + with: + name: unsigned-build-artifacts + path: out + if-no-files-found: error diff --git a/.pipelines/CosmosDB-Shell-Official.yml b/.pipelines/CosmosDB-Shell-Official.yml index 794a639..3c0ca7c 100644 --- a/.pipelines/CosmosDB-Shell-Official.yml +++ b/.pipelines/CosmosDB-Shell-Official.yml @@ -9,7 +9,9 @@ ################################################################################# trigger: # https://aka.ms/obpipelines/triggers - - main + branches: + include: + - "main" parameters: # parameters are shown up in ADO UI in a build queue time - name: "debug" @@ -28,7 +30,6 @@ variables: BuildSolution: $(Build.SourcesDirectory)\CosmosDBShell.sln ReleaseProject: $(Build.SourcesDirectory)\CosmosDBShell\CosmosDBShell.csproj BuildConfiguration: Release - OneES_SbomNugetSDLPath: out\nupkg WindowsContainerImage: "onebranch.azurecr.io/windows/ltsc2022/vse2022:latest" # Docker image which is used to build the project https://aka.ms/obpipelines/containers @@ -68,6 +69,7 @@ extends: variables: # More settings at https://aka.ms/obpipelines/yaml/jobs ob_outputDirectory: '$(Build.SourcesDirectory)\out' # this directory is uploaded to pipeline artifacts, reddog and cloudvault. More info at https://aka.ms/obpipelines/artifacts ob_artifactBaseName: cosmos_shell_all # combined artifact with all RIDs for SDL scanning + OneES_SbomNugetSDLPath: out\nupkg # https://aka.ms/obpipelines/sdl ob_sdl_binskim_enabled: true ob_sdl_binskim_scanOutputDirectoryOnly: true @@ -98,6 +100,19 @@ extends: inputs: targetType: inline script: | + function Normalize-NuGetVersion([string]$value) { + if ([string]::IsNullOrWhiteSpace($value)) { + return $value + } + + if ($value -match '^(?\d+(?:\.\d+)*)(?[-+].*)?$') { + $normalizedCore = (($Matches.core -split '\.') | ForEach-Object { [string]([int]$_) }) -join '.' + return "$normalizedCore$($Matches.suffix)" + } + + return $value + } + $buildNumber = "$(Build.BuildNumber)" Write-Host "Build.BuildNumber=$buildNumber" @@ -106,6 +121,14 @@ extends: $version = "1.0.$(Build.BuildId)" } + $version = Normalize-NuGetVersion $version + + if ($buildNumber -ne $version) { + Write-Host "Updating Build.BuildNumber to normalized NuGet version: $version" + Write-Host "##vso[build.updatebuildnumber]$version" + $buildNumber = $version + } + $fileVersion = $version $parts = $fileVersion.Split('.') if ($parts.Count -eq 2) { @@ -303,12 +326,13 @@ extends: condition: succeeded() inputs: command: "pack" - projects: "$(ReleaseProject)" + packagesToPack: "$(ReleaseProject)" + configurationToPack: "$(BuildConfiguration)" + nobuild: true outputDir: '$(Build.SourcesDirectory)\out\nupkg' - arguments: > - --configuration $(BuildConfiguration) --no-build --no-restore - /p:PackageVersion=$(Build.BuildNumber) - /p:ContinuousIntegrationBuild=true + versioningScheme: byEnvVar + versionEnvVar: CosmosDBShell_Version + buildProperties: "Version=$(CosmosDBShell_Version);PackageVersion=$(CosmosDBShell_Version);FileVersion=$(CosmosDBShell_FileVersion);InformationalVersion=$(CosmosDBShell_InformationalVersion);ContinuousIntegrationBuild=true" - task: PowerShell@2 displayName: "Expand RID packages for payload signing" @@ -461,6 +485,18 @@ extends: # With ToolPackageRuntimeIdentifiers, dotnet pack produces per-RID packages # (e.g. CosmosDBShell.win-x64.*.nupkg) plus a pointer package (CosmosDBShell.*.nupkg). # All RID packages must be available before the pointer package is published. + - task: PowerShell@2 + displayName: "List NuGet packages before publish" + condition: succeeded() + inputs: + targetType: inline + script: | + $pkgDir = "$(Build.SourcesDirectory)\out\nupkg" + Write-Host "NuGet packages in $pkgDir" + Get-ChildItem -Path $pkgDir -Filter *.nupkg -File | Sort-Object Name | ForEach-Object { + Write-Host " - $($_.Name) [$($_.Length) bytes]" + } + - task: NuGetCommand@2 displayName: "Push RID-specific NuGet packages" condition: and(succeeded(), @@ -468,9 +504,14 @@ extends: eq('${{ parameters.publishNuget }}', 'true')) inputs: command: "push" - packagesToPush: "$(Build.SourcesDirectory)\\out\\nupkg\\CosmosDBShell.{win-x64,linux-x64,linux-arm64,osx-x64,osx-arm64}.*.nupkg" + packagesToPush: | + $(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.win-x64.*.nupkg + $(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.linux-x64.*.nupkg + $(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.linux-arm64.*.nupkg + $(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.osx-x64.*.nupkg + $(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.osx-arm64.*.nupkg nuGetFeedType: "internal" - publishVstsFeed: "CosmosDB/CosmosDB_CosmosShell" + publishVstsFeed: "CosmosDB/CosmosDBShell" allowPackageConflicts: true - task: NuGetCommand@2 displayName: "Push pointer NuGet package" @@ -479,9 +520,9 @@ extends: eq('${{ parameters.publishNuget }}', 'true')) inputs: command: "push" - packagesToPush: "$(Build.SourcesDirectory)\\out\\nupkg\\CosmosDBShell.$(Build.BuildNumber).nupkg" + packagesToPush: "$(Build.SourcesDirectory)\\out\\nupkg\\CosmosDBShell.$(CosmosDBShell_Version).nupkg" nuGetFeedType: "internal" - publishVstsFeed: "CosmosDB/CosmosDB_CosmosShell" + publishVstsFeed: "CosmosDB/CosmosDBShell" allowPackageConflicts: true - job: CodeQLAnalyze @@ -540,3 +581,26 @@ extends: Write-Host "Copying from $source to $dest" New-Item -ItemType Directory -Path $dest -Force | Out-Null Copy-Item -Path "$source\*" -Destination $dest -Recurse -Force + + - task: PowerShell@2 + displayName: "Validate package output for SBOM" + inputs: + targetType: inline + script: | + $outputDir = "$(ob_outputDirectory)" + + if (-not (Test-Path $outputDir)) { + Write-Error "Package output directory not found: $outputDir" + exit 1 + } + + $files = Get-ChildItem -Path $outputDir -Recurse -File -ErrorAction SilentlyContinue + if (-not $files -or $files.Count -eq 0) { + Write-Error "Package output directory is empty: $outputDir" + exit 1 + } + + Write-Host "Package output contains $($files.Count) file(s):" + $files | Sort-Object FullName | Select-Object -First 50 | ForEach-Object { + Write-Host " - $($_.FullName) [$($_.Length) bytes]" + } diff --git a/.pipelines/CosmosDB-Shell-PullRequest.yml b/.pipelines/CosmosDB-Shell-PullRequest.yml deleted file mode 100644 index d02a201..0000000 --- a/.pipelines/CosmosDB-Shell-PullRequest.yml +++ /dev/null @@ -1,473 +0,0 @@ -################################################################################# -# OneBranch Pipelines - PR Build # -# This pipeline was created by EasyStart from a sample located at: # -# https://aka.ms/obpipelines/easystart/samples # -# Documentation: https://aka.ms/obpipelines # -# Yaml Schema: https://aka.ms/obpipelines/yaml/schema # -# Retail Tasks: https://aka.ms/obpipelines/tasks # -# Support: https://aka.ms/onebranchsup # -################################################################################# - -trigger: none # https://aka.ms/obpipelines/triggers - -parameters: # parameters are shown up in ADO UI in a build queue time - - name: "debug" - displayName: "Enable debug output" - type: boolean - default: false - -variables: - CDP_DEFINITION_BUILD_COUNT: $[counter('', 0)] # needed for onebranch.pipeline.version task https://aka.ms/obpipelines/versioning - system.debug: ${{ parameters.debug }} - - BuildSolution: $(Build.SourcesDirectory)\CosmosDBShell.sln - ReleaseProject: $(Build.SourcesDirectory)\CosmosDBShell\CosmosDBShell.csproj - BuildConfiguration: Release - OneES_SbomNugetSDLPath: out\nupkg - - WindowsContainerImage: "onebranch.azurecr.io/windows/ltsc2022/vse2022:latest" # Docker image which is used to build the project https://aka.ms/obpipelines/containers - -resources: - repositories: - - repository: templates - type: git - name: OneBranch.Pipelines/GovernedTemplates - ref: refs/heads/main - -extends: - template: v2/OneBranch.NonOfficial.CrossPlat.yml@templates # https://aka.ms/obpipelines/templates - parameters: - featureFlags: - WindowsHostVersion: - Version: 2022 - Network: R1 - globalSdl: # https://aka.ms/obpipelines/sdl - # tsa: - # enabled: true # SDL results of non-official builds aren't uploaded to TSA by default. - # credscan: - # suppressionsFile: $(Build.SourcesDirectory)\.config\CredScanSuppressions.json - policheck: - break: true # always break the build on policheck issues. You can disable it by setting to 'false' - # suppression: - # suppressionFile: $(Build.SourcesDirectory)\.gdn\global.gdnsuppress - - stages: - - stage: build - jobs: - - job: main - pool: - type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs - - variables: - ob_outputDirectory: '$(Build.SourcesDirectory)\out' # this directory is uploaded to pipeline artifacts, reddog and cloudvault. More info at https://aka.ms/obpipelines/artifacts - ob_artifactBaseName: cosmos_shell_all # combined artifact with all RIDs for SDL scanning - # https://aka.ms/obpipelines/sdl - ob_sdl_binskim_enabled: true # you can disable sdl tools in non-official build - ob_sdl_binskim_break: true # always break the build on binskim issues. You can disable it by setting to 'false' - ob_sdl_binskim_scanOutputDirectoryOnly: true - ob_sdl_roslyn_break: true - # ob_sdl_suppression_suppressionFile: $(Build.SourcesDirectory)\.gdn\job.gdnsuppress - - steps: - - task: UseDotNet@2 - continueOnError: true - inputs: - packageType: "sdk" - useGlobalJson: true - performMultiLevelLookup: true - - - task: onebranch.pipeline.version@1 # generates automatic version. For other versioning options check https://aka.ms/obpipelines/versioning - displayName: "Setup BuildNumber" - inputs: - system: "RevisionCounter" - major: "1" - minor: "0" - exclude_commit: true - - - task: PowerShell@2 - displayName: "Compute version properties" - inputs: - targetType: inline - script: | - $buildNumber = "$(Build.BuildNumber)" - Write-Host "Build.BuildNumber=$buildNumber" - - $version = $buildNumber - if (-not ($version -match '^\d+(\.\d+){1,3}$')) { - $version = "1.0.$(Build.BuildId)" - } - - $fileVersion = $version - $parts = $fileVersion.Split('.') - if ($parts.Count -eq 2) { - $fileVersion = "$fileVersion.$(Build.BuildId).0" - } elseif ($parts.Count -eq 3) { - $fileVersion = "$fileVersion.$(Build.BuildId)" - } elseif ($parts.Count -gt 4) { - $fileVersion = ($parts[0..3] -join '.') - } - - $infoVersion = "$buildNumber+$(Build.SourceVersion)" - - Write-Host "##vso[task.setvariable variable=CosmosDBShell_Version]$version" - Write-Host "##vso[task.setvariable variable=CosmosDBShell_FileVersion]$fileVersion" - Write-Host "##vso[task.setvariable variable=CosmosDBShell_InformationalVersion]$infoVersion" - - - task: DotNetCoreCLI@2 - displayName: "DotNetCore restore" - inputs: - command: "custom" - projects: $(BuildSolution) - custom: "restore" - - # roslynanalyzers task wraps around dotnet build to enable static analysis - - task: RoslynAnalyzers@3 - displayName: "DotNetCore build with RoslynAnalyzers" - inputs: - userProvideBuildInfo: "msBuildInfo" - msBuildCommandline: "dotnet.exe build $(BuildSolution) --no-restore --configuration $(BuildConfiguration)" - - - task: DotNetCoreCLI@2 - displayName: "DotNetCore test" - inputs: - command: "test" - projects: $(BuildSolution) - arguments: '--no-build --no-restore --configuration $(BuildConfiguration) --logger trx --blame --collect "Code coverage" --results-directory $(Build.SourcesDirectory)\TestResults\' - publishTestResults: false - - - task: PublishTestResults@2 - displayName: "Publish test results" - inputs: - testResultsFormat: VSTest - testResultsFiles: '$(Build.SourcesDirectory)\TestResults\**\*.trx' - failTaskOnFailedTests: true - - - task: DotNetCoreCLI@2 - displayName: "DotNetCore publish (Windows)" - inputs: - command: "publish" - publishWebProjects: false - projects: $(ReleaseProject) - arguments: '--configuration $(BuildConfiguration) -r win-x64 --output $(Build.SourcesDirectory)\out\win-x64 /p:Version=$(CosmosDBShell_Version) /p:FileVersion=$(CosmosDBShell_FileVersion) /p:InformationalVersion=$(CosmosDBShell_InformationalVersion)' - zipAfterPublish: false - - - task: DotNetCoreCLI@2 - displayName: "DotNetCore publish (MacOS)" - inputs: - command: "publish" - publishWebProjects: false - projects: $(ReleaseProject) - arguments: '--configuration $(BuildConfiguration) -r osx-x64 --output $(Build.SourcesDirectory)\out\osx-x64 /p:Version=$(CosmosDBShell_Version) /p:FileVersion=$(CosmosDBShell_FileVersion) /p:InformationalVersion=$(CosmosDBShell_InformationalVersion)' - zipAfterPublish: false - - - task: DotNetCoreCLI@2 - displayName: "DotNetCore publish (Linux)" - inputs: - command: "publish" - publishWebProjects: false - projects: $(ReleaseProject) - arguments: '--configuration $(BuildConfiguration) -r linux-x64 --output $(Build.SourcesDirectory)\out\linux-x64 /p:Version=$(CosmosDBShell_Version) /p:FileVersion=$(CosmosDBShell_FileVersion) /p:InformationalVersion=$(CosmosDBShell_InformationalVersion)' - zipAfterPublish: false - - - task: DotNetCoreCLI@2 - displayName: "DotNetCore publish (Linux ARM64)" - inputs: - command: "publish" - publishWebProjects: false - projects: $(ReleaseProject) - arguments: '--configuration $(BuildConfiguration) -r linux-arm64 --output $(Build.SourcesDirectory)\out\linux-arm64 /p:Version=$(CosmosDBShell_Version) /p:FileVersion=$(CosmosDBShell_FileVersion) /p:InformationalVersion=$(CosmosDBShell_InformationalVersion)' - zipAfterPublish: false - - - task: DotNetCoreCLI@2 - displayName: "DotNetCore publish (MacOS ARM64)" - inputs: - command: "publish" - publishWebProjects: false - projects: $(ReleaseProject) - arguments: '--configuration $(BuildConfiguration) -r osx-arm64 --output $(Build.SourcesDirectory)\out\osx-arm64 /p:Version=$(CosmosDBShell_Version) /p:FileVersion=$(CosmosDBShell_FileVersion) /p:InformationalVersion=$(CosmosDBShell_InformationalVersion)' - zipAfterPublish: false - - # Kept for reference, intentionally disabled: - # - task: DotNetCoreCLI@2 - # displayName: "DotNetCore publish (Any)" - # inputs: - # command: "publish" - # publishWebProjects: false - # projects: $(ReleaseProject) - # arguments: '--configuration $(BuildConfiguration) -r any --output $(Build.SourcesDirectory)\out\any /p:Version=$(CosmosDBShell_Version) /p:FileVersion=$(CosmosDBShell_FileVersion) /p:InformationalVersion=$(CosmosDBShell_InformationalVersion)' - # zipAfterPublish: false - - - task: onebranch.pipeline.signing@1 - displayName: "Sign publish output" - inputs: - command: "sign" - use_testsign: true - signing_profile: "external_distribution" - files_to_sign: "**/*.exe;**/*.dll" - search_root: '$(Build.SourcesDirectory)\out' - - - task: DotNetCoreCLI@2 - displayName: "Build Fuzzer" - inputs: - command: "build" - projects: "$(Build.SourcesDirectory)\\CosmosDBShell.Fuzzer\\CosmosDBShell.Fuzzer.csproj" - arguments: "--configuration $(BuildConfiguration) --no-restore" - - - task: PowerShell@2 - displayName: "Clean NuGet package output" - condition: succeeded() - inputs: - targetType: inline - script: | - $pkgDir = "$(Build.SourcesDirectory)\out\nupkg" - New-Item -ItemType Directory -Path $pkgDir -Force | Out-Null - Get-ChildItem -Path $pkgDir -Filter *.nupkg -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue - - # Note: signing bin/ IL assemblies before pack is ineffective here. - # PublishSingleFile=true causes dotnet pack to produce a new single-file - # EXE that bundles all assemblies — the input signatures are lost. - # The actual payload signing happens post-pack via extract/sign/repack below. - - - task: DotNetCoreCLI@2 - displayName: "Pack CosmosDBShell NuGet" - condition: succeeded() - inputs: - command: "pack" - projects: "$(ReleaseProject)" - outputDir: '$(Build.SourcesDirectory)\out\nupkg' - arguments: > - --configuration $(BuildConfiguration) --no-build --no-restore - /p:PackageVersion=$(CosmosDBShell_Version) - /p:ContinuousIntegrationBuild=true - - - task: PowerShell@2 - displayName: "Expand RID packages for payload signing" - condition: succeeded() - inputs: - targetType: inline - script: | - $pkgDir = "$(Build.SourcesDirectory)\out\nupkg" - $signStage = "$(Build.SourcesDirectory)\out\nupkg-payload" - - if (Test-Path $signStage) { - Remove-Item -Recurse -Force $signStage - } - - New-Item -ItemType Directory -Path $signStage -Force | Out-Null - Add-Type -AssemblyName System.IO.Compression.FileSystem - - $ridPackages = Get-ChildItem -Path $pkgDir -Filter "CosmosDBShell.*.nupkg" -File -ErrorAction SilentlyContinue | Where-Object { - $_.Name -match '^CosmosDBShell\.(win-x64|linux-x64|linux-arm64|osx-x64|osx-arm64)\..+\.nupkg$' - } - - if (-not $ridPackages -or $ridPackages.Count -eq 0) { - Write-Error "No RID-specific packages found to expand for payload signing." - exit 1 - } - - foreach ($pkg in $ridPackages) { - $targetDir = Join-Path $signStage $pkg.BaseName - [System.IO.Compression.ZipFile]::ExtractToDirectory($pkg.FullName, $targetDir) - } - - - task: onebranch.pipeline.signing@1 - displayName: "Sign NuGet payload binaries" - condition: succeeded() - inputs: - command: "sign" - use_testsign: true - signing_profile: "external_distribution" - files_to_sign: "**/*.exe;**/*.dll" - search_root: '$(Build.SourcesDirectory)\out\nupkg-payload' - - - task: PowerShell@2 - displayName: "Repack RID packages with signed payload" - condition: succeeded() - inputs: - targetType: inline - script: | - $pkgDir = "$(Build.SourcesDirectory)\out\nupkg" - $signStage = "$(Build.SourcesDirectory)\out\nupkg-payload" - - Add-Type -AssemblyName System.IO.Compression.FileSystem - - $stagedPackages = Get-ChildItem -Path $signStage -Directory -ErrorAction SilentlyContinue - if (-not $stagedPackages -or $stagedPackages.Count -eq 0) { - Write-Error "No expanded packages found to repack." - exit 1 - } - - foreach ($staged in $stagedPackages) { - $packagePath = Join-Path $pkgDir ($staged.Name + ".nupkg") - - if (-not (Test-Path $packagePath)) { - Write-Error "Expected package not found for repack: $packagePath" - exit 1 - } - - Remove-Item -Path $packagePath -Force - [System.IO.Compression.ZipFile]::CreateFromDirectory($staged.FullName, $packagePath, [System.IO.Compression.CompressionLevel]::Optimal, $false) - } - - - task: PowerShell@2 - displayName: "Validate NuGet package set" - condition: succeeded() - inputs: - targetType: inline - script: | - $pkgDir = "$(Build.SourcesDirectory)\out\nupkg" - $ridPatterns = @( - "CosmosDBShell.win-x64.*.nupkg", - "CosmosDBShell.linux-x64.*.nupkg", - "CosmosDBShell.linux-arm64.*.nupkg", - "CosmosDBShell.osx-x64.*.nupkg", - "CosmosDBShell.osx-arm64.*.nupkg" - ) - - foreach ($pattern in $ridPatterns) { - $matches = Get-ChildItem -Path (Join-Path $pkgDir $pattern) -ErrorAction SilentlyContinue - if (-not $matches -or $matches.Count -eq 0) { - Write-Error "Expected package was not generated: $pattern" - exit 1 - } - } - - $allPackages = Get-ChildItem -Path (Join-Path $pkgDir "CosmosDBShell.*.nupkg") -ErrorAction SilentlyContinue - $pointerPackages = $allPackages | Where-Object { - $_.Name -notmatch '^CosmosDBShell\.(win-x64|linux-x64|linux-arm64|osx-x64|osx-arm64)\..+\.nupkg$' - } - - if (-not $pointerPackages -or $pointerPackages.Count -ne 1) { - $names = @($pointerPackages | ForEach-Object { $_.Name }) - Write-Error "Expected exactly one pointer package (non-RID). Found: $($names -join ', ')" - exit 1 - } - - $anyMatches = Get-ChildItem -Path (Join-Path $pkgDir "CosmosDBShell.any.*.nupkg") -ErrorAction SilentlyContinue - if ($anyMatches -and $anyMatches.Count -gt 0) { - $names = $anyMatches | ForEach-Object { $_.Name } | Sort-Object - Write-Error "Unexpected any-RID package(s) found: $($names -join ', ')" - exit 1 - } - - - task: onebranch.pipeline.signing@1 - displayName: "Sign NuGet packages" - condition: succeeded() - inputs: - command: "sign" - signing_profile: "external_distribution" - use_testsign: true - cp_code: "CP-401405" - files_to_sign: "**/*.nupkg" - search_root: '$(Build.SourcesDirectory)\out\nupkg' - - - task: PowerShell@2 - displayName: "Run Fuzzer Smoke Test" - inputs: - targetType: inline - script: | - Write-Host "Starting fuzz smoke test..." - # Stable artifact directory - $artifactFindings = "$(Build.SourcesDirectory)\\fuzz-findings" - if (Test-Path $artifactFindings) { - Remove-Item -Recurse -Force $artifactFindings - } - - Write-Host "Running fuzz harness" - cd "$(Build.SourcesDirectory)\\CosmosDBShell.Fuzzer" - dotnet run --no-build --configuration $(BuildConfiguration) -- --all - - # Check if the fuzzer created findings in the expected location - $sourceFindingsDir = "$(Build.SourcesDirectory)\\CosmosDBShell.Fuzzer\\findings" - - $hasCrashes = $false - $crashCount = 0 - - # Check both possible locations - foreach ($findingsPath in $sourceFindingsDir) { - if (Test-Path $findingsPath) { - $crashesDir = Join-Path $findingsPath "crashes" - if (Test-Path $crashesDir) { - $crashFiles = Get-ChildItem $crashesDir -File - if ($crashFiles.Count -gt 0) { - $hasCrashes = $true - $crashCount = $crashFiles.Count - Write-Host "Found $crashCount crash(es) at $crashesDir" - - # Copy findings to artifact directory for publishing - New-Item -ItemType Directory -Path $artifactFindings -Force | Out-Null - Copy-Item $findingsPath\* $artifactFindings -Recurse -Force - break - } - } - } - } - - if ($hasCrashes) { - echo "##vso[task.logissue type=error]Fuzzer detected $crashCount crash(es)." - echo "##vso[task.setvariable variable=FuzzerCrashes]$crashCount" - echo "##vso[task.setvariable variable=HasFuzzFindings]true" - } else { - Write-Host "No crashes detected during fuzz testing." - echo "##vso[task.setvariable variable=HasFuzzFindings]false" - } - - - task: PowerShell@2 - displayName: "Fail on Fuzz Crashes" - condition: and(succeededOrFailed(), eq(variables['HasFuzzFindings'], 'true')) - inputs: - targetType: inline - script: | - if ($env:FuzzerCrashes -and [int]$env:FuzzerCrashes -gt 0) { - Write-Error "Failing build due to $($env:FuzzerCrashes) fuzz crash(es)." - exit 1 - } - - - job: CodeQLAnalyze - displayName: CodeQL (C#) - pool: - vmImage: ubuntu-latest - steps: - - checkout: self - persistCredentials: true - - task: CodeQL3000Init@0 - inputs: - languages: "csharp" - - script: | - dotnet restore CosmosDBShell.sln - dotnet build CosmosDBShell.sln -c Release --no-incremental - displayName: Build for CodeQL - - task: CodeQL3000Finalize@0 - displayName: Finalize CodeQL - - - stage: package - displayName: Package ArtifactsFre - dependsOn: build - jobs: - - job: artifacts - displayName: Package - pool: - type: windows - variables: - ob_outputDirectory: '$(Build.SourcesDirectory)\package' - ob_artifactBaseName: cosmos_shell - ob_artifactSuffix: _nupkg_$(Build.BuildNumber) - steps: - - download: current - artifact: cosmos_shell_all - displayName: Download build artifacts - - - task: PowerShell@2 - displayName: "Copy NuGet packages to output" - inputs: - targetType: inline - script: | - $source = "$(Pipeline.Workspace)\cosmos_shell_all\nupkg" - $dest = "$(ob_outputDirectory)" - Write-Host "Copying from $source to $dest" - if (-not (Test-Path $source)) { - Write-Error "NuGet package directory not found: $source" - exit 1 - } - New-Item -ItemType Directory -Path $dest -Force | Out-Null - Copy-Item -Path "$source\*.nupkg" -Destination $dest -Force diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d148cb..e6703b2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,9 +20,12 @@ There are several ways you can contribute to the CosmosDBShell project: - **Prerequisites**: [.NET SDK 10.0+](https://dotnet.microsoft.com/download) - Clone the repository and open it in VS Code or your preferred IDE. - Restore dependencies: `dotnet restore CosmosDBShell.sln` - - Build: `dotnet build CosmosDBShell.sln` (or press Ctrl+Shift+B in VS Code). + - Build: `dotnet build CosmosDBShell.sln` (or use the VS Code build task with Ctrl+Shift+B). - Run tests: `dotnet test CosmosDBShell.sln` - Run the tool locally: `dotnet run --project CosmosDBShell/CosmosDBShell.csproj` + - GitHub Actions runs CI from [.github/workflows/ci.yml](.github/workflows/ci.yml) and uploads unsigned artifacts from [.github/workflows/package-unsigned.yml](.github/workflows/package-unsigned.yml). + - GitHub Actions uses [.github/nuget.github.config](.github/nuget.github.config) so it can restore from nuget.org independently of Azure Pipelines. + - Azure Pipelines can continue independently for branch-based signing and publishing. This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) diff --git a/README.md b/README.md index 533c9c0..1c13f34 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,17 @@ dotnet tool uninstall --global - [Programming](docs/programming.md) - Variables, control flow, functions - [MCP](docs/mcp.md) - Model Context Protocol integration +## CI And Packaging + +GitHub Actions handles PR validation and unsigned package creation: + +- [.github/workflows/ci.yml](.github/workflows/ci.yml): restore, build, test, and fuzzer smoke test (runs on every PR) +- [.github/workflows/package-unsigned.yml](.github/workflows/package-unsigned.yml): build and upload unsigned NuGet artifacts (runs on tags) + +GitHub Actions uses [.github/nuget.github.config](.github/nuget.github.config) so the workflows restore packages from nuget.org without depending on the Azure DevOps feed. + +Azure Pipelines ([.pipelines/CosmosDB-Shell-Official.yml](.pipelines/CosmosDB-Shell-Official.yml)) handles signing and publishing via the internal Azure setup. + ## CLI Arguments | Option | Description | From 99a048cef5ad0e30d08ae992569e88939fc02e58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 12:38:43 +0000 Subject: [PATCH 02/30] Initial plan From 2408472d8bf3b2efb6c7bfd58b00f474b764741b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 12:41:30 +0000 Subject: [PATCH 03/30] Fix informational_version double-+ when tag already has build metadata Co-authored-by: mkrueger <341098+mkrueger@users.noreply.github.com> --- .github/workflows/package-unsigned.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/package-unsigned.yml b/.github/workflows/package-unsigned.yml index 73feffd..c5f46b0 100644 --- a/.github/workflows/package-unsigned.yml +++ b/.github/workflows/package-unsigned.yml @@ -76,7 +76,11 @@ jobs: $fileVersion = "$major.$minor.$build.$revision" } - $infoVersion = "$version+${{ github.sha }}" + if ($version -match '\+') { + $infoVersion = "$version.${{ github.sha }}" + } else { + $infoVersion = "$version+${{ github.sha }}" + } "package_version=$version" >> $env:GITHUB_OUTPUT "file_version=$fileVersion" >> $env:GITHUB_OUTPUT From 919a10981cd2565709c234a0fd203a785aa2b603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Fri, 20 Mar 2026 14:15:23 +0100 Subject: [PATCH 04/30] Added uninstallation instructions in README. --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c13f34..f619644 100644 --- a/README.md +++ b/README.md @@ -99,8 +99,28 @@ dotnet tool update --global --add-source /path/to/nupkgs --version Uninstall: +List the installed global tools first so you can identify the exact package ID: + +```bash +dotnet tool list --global +``` + +Then uninstall the matching package ID. For example, if you installed the Windows x64 RID-specific package: + +```powershell +dotnet tool uninstall --global CosmosDBShell.win-x64 +``` + +If you installed the base package instead: + +```bash +dotnet tool uninstall --global CosmosDBShell +``` + +If you are not sure which package ID is installed, list global tools first: + ```bash -dotnet tool uninstall --global +dotnet tool list --global ``` ## Documentation From 1d729177cbfc6952514d892e0b67c1165eafed5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Mon, 23 Mar 2026 08:25:50 +0100 Subject: [PATCH 05/30] Added "preview" suffix. --- .pipelines/CosmosDB-Shell-Official.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.pipelines/CosmosDB-Shell-Official.yml b/.pipelines/CosmosDB-Shell-Official.yml index 3c0ca7c..aabebcb 100644 --- a/.pipelines/CosmosDB-Shell-Official.yml +++ b/.pipelines/CosmosDB-Shell-Official.yml @@ -123,6 +123,8 @@ extends: $version = Normalize-NuGetVersion $version + $packageVersion = "$version-preview" + if ($buildNumber -ne $version) { Write-Host "Updating Build.BuildNumber to normalized NuGet version: $version" Write-Host "##vso[build.updatebuildnumber]$version" @@ -142,6 +144,7 @@ extends: $infoVersion = "$buildNumber+$(Build.SourceVersion)" Write-Host "##vso[task.setvariable variable=CosmosDBShell_Version]$version" + Write-Host "##vso[task.setvariable variable=CosmosDBShell_PackageVersion]$packageVersion" Write-Host "##vso[task.setvariable variable=CosmosDBShell_FileVersion]$fileVersion" Write-Host "##vso[task.setvariable variable=CosmosDBShell_InformationalVersion]$infoVersion" @@ -331,8 +334,8 @@ extends: nobuild: true outputDir: '$(Build.SourcesDirectory)\out\nupkg' versioningScheme: byEnvVar - versionEnvVar: CosmosDBShell_Version - buildProperties: "Version=$(CosmosDBShell_Version);PackageVersion=$(CosmosDBShell_Version);FileVersion=$(CosmosDBShell_FileVersion);InformationalVersion=$(CosmosDBShell_InformationalVersion);ContinuousIntegrationBuild=true" + versionEnvVar: CosmosDBShell_PackageVersion + buildProperties: "Version=$(CosmosDBShell_Version);PackageVersion=$(CosmosDBShell_PackageVersion);FileVersion=$(CosmosDBShell_FileVersion);InformationalVersion=$(CosmosDBShell_InformationalVersion);ContinuousIntegrationBuild=true" - task: PowerShell@2 displayName: "Expand RID packages for payload signing" @@ -520,7 +523,7 @@ extends: eq('${{ parameters.publishNuget }}', 'true')) inputs: command: "push" - packagesToPush: "$(Build.SourcesDirectory)\\out\\nupkg\\CosmosDBShell.$(CosmosDBShell_Version).nupkg" + packagesToPush: "$(Build.SourcesDirectory)\\out\\nupkg\\CosmosDBShell.$(CosmosDBShell_PackageVersion).nupkg" nuGetFeedType: "internal" publishVstsFeed: "CosmosDB/CosmosDBShell" allowPackageConflicts: true From 0f4b096984211fb0d41162b4675afde6cd788335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Mon, 23 Mar 2026 08:45:28 +0100 Subject: [PATCH 06/30] Added packages for branches in workflow --- .github/workflows/package-branches.yml | 181 +++++++++++++++++++++++++ README.md | 3 + 2 files changed, 184 insertions(+) create mode 100644 .github/workflows/package-branches.yml diff --git a/.github/workflows/package-branches.yml b/.github/workflows/package-branches.yml new file mode 100644 index 0000000..37b79f5 --- /dev/null +++ b/.github/workflows/package-branches.yml @@ -0,0 +1,181 @@ +name: Package Branch Tool Artifacts + +on: + workflow_dispatch: + push: + branches-ignore: + - main + +concurrency: + group: package-branches-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: read + +env: + BUILD_CONFIGURATION: Release + +jobs: + package: + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + cache: true + cache-dependency-path: | + global.json + Directory.Packages.props + **/*.csproj + + - name: Compute version properties + id: version + shell: pwsh + run: | + $runNumber = [int]"${{ github.run_number }}" + $assemblyVersion = "1.0.$runNumber" + $branchName = "${{ github.ref_name }}" + $branchLabel = $branchName.ToLowerInvariant() + $branchLabel = $branchLabel -replace '[^0-9a-z-]', '-' + $branchLabel = $branchLabel -replace '-+', '-' + $branchLabel = $branchLabel.Trim('-') + + if ([string]::IsNullOrWhiteSpace($branchLabel)) { + $branchLabel = 'branch' + } + + if ($branchLabel.Length -gt 40) { + $branchLabel = $branchLabel.Substring(0, 40).Trim('-') + } + + $packageVersion = "$assemblyVersion-preview.$branchLabel" + $fileVersion = "1.0.$runNumber.0" + $infoVersion = "$packageVersion+${{ github.sha }}" + + "assembly_version=$assemblyVersion" >> $env:GITHUB_OUTPUT + "package_version=$packageVersion" >> $env:GITHUB_OUTPUT + "file_version=$fileVersion" >> $env:GITHUB_OUTPUT + "informational_version=$infoVersion" >> $env:GITHUB_OUTPUT + "branch_label=$branchLabel" >> $env:GITHUB_OUTPUT + "artifact_name=branch-tool-packages-$branchLabel-${{ github.run_number }}" >> $env:GITHUB_OUTPUT + + - name: Restore + run: dotnet restore CosmosDBShell.sln --configfile .github/nuget.github.config + + - name: Build solution + run: dotnet build CosmosDBShell.sln --configuration $env:BUILD_CONFIGURATION --no-restore + shell: pwsh + + - name: Publish runtime artifacts + shell: pwsh + run: | + $rids = @('win-x64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64') + foreach ($rid in $rids) { + dotnet publish CosmosDBShell/CosmosDBShell.csproj ` + --configuration $env:BUILD_CONFIGURATION ` + --no-restore ` + -r $rid ` + --output "out/$rid" ` + /p:Version=${{ steps.version.outputs.assembly_version }} ` + /p:FileVersion=${{ steps.version.outputs.file_version }} ` + /p:InformationalVersion=${{ steps.version.outputs.informational_version }} + } + + - name: Pack branch NuGet artifacts + shell: pwsh + run: | + New-Item -ItemType Directory -Path out/nupkg -Force | Out-Null + dotnet pack CosmosDBShell/CosmosDBShell.csproj ` + --configuration $env:BUILD_CONFIGURATION ` + --no-build ` + --no-restore ` + --output out/nupkg ` + /p:PackageVersion=${{ steps.version.outputs.package_version }} ` + /p:Version=${{ steps.version.outputs.assembly_version }} ` + /p:FileVersion=${{ steps.version.outputs.file_version }} ` + /p:InformationalVersion=${{ steps.version.outputs.informational_version }} ` + /p:ContinuousIntegrationBuild=true + + - name: Validate NuGet package set + shell: pwsh + run: | + $pkgDir = Join-Path $pwd 'out/nupkg' + $ridPatterns = @( + 'CosmosDBShell.win-x64.*.nupkg', + 'CosmosDBShell.linux-x64.*.nupkg', + 'CosmosDBShell.linux-arm64.*.nupkg', + 'CosmosDBShell.osx-x64.*.nupkg', + 'CosmosDBShell.osx-arm64.*.nupkg' + ) + + foreach ($pattern in $ridPatterns) { + $matches = Get-ChildItem -Path (Join-Path $pkgDir $pattern) -ErrorAction SilentlyContinue + if (-not $matches -or $matches.Count -eq 0) { + Write-Error "Expected package was not generated: $pattern" + exit 1 + } + } + + $allPackages = Get-ChildItem -Path (Join-Path $pkgDir 'CosmosDBShell.*.nupkg') -ErrorAction SilentlyContinue + $pointerPackages = $allPackages | Where-Object { + $_.Name -notmatch '^CosmosDBShell\.(win-x64|linux-x64|linux-arm64|osx-x64|osx-arm64)\..+\.nupkg$' + } + + if (-not $pointerPackages -or $pointerPackages.Count -ne 1) { + $names = @($pointerPackages | ForEach-Object { $_.Name }) + Write-Error "Expected exactly one pointer package (non-RID). Found: $($names -join ', ')" + exit 1 + } + + $anyMatches = Get-ChildItem -Path (Join-Path $pkgDir 'CosmosDBShell.any.*.nupkg') -ErrorAction SilentlyContinue + if ($anyMatches -and $anyMatches.Count -gt 0) { + $names = $anyMatches | ForEach-Object { $_.Name } | Sort-Object + Write-Error "Unexpected any-RID package(s) found: $($names -join ', ')" + exit 1 + } + + - name: List packaged NuGet files + shell: pwsh + run: | + Get-ChildItem -Path out/nupkg -Filter *.nupkg -File | Sort-Object Name | ForEach-Object { + Write-Host " - $($_.Name) [$($_.Length) bytes]" + } + + - name: Write package install summary + shell: pwsh + run: | + $summary = $env:GITHUB_STEP_SUMMARY + $version = '${{ steps.version.outputs.package_version }}' + $artifactName = '${{ steps.version.outputs.artifact_name }}' + $lines = @( + '## Branch tool packages', + '', + "- Branch: `${{ github.ref_name }}`", + "- Preview version: `$version`", + "- Artifact: `$artifactName`", + '', + 'Download the artifact, extract the `.nupkg` files to a local folder, then install one of these packages:', + '', + '```powershell', + 'dotnet tool install --global CosmosDBShell --add-source C:\path\to\nupkgs --version ' + $version, + 'dotnet tool install --global CosmosDBShell.win-x64 --add-source C:\path\to\nupkgs --version ' + $version, + 'dotnet tool install --global CosmosDBShell.linux-x64 --add-source C:\path\to\nupkgs --version ' + $version, + 'dotnet tool install --global CosmosDBShell.linux-arm64 --add-source C:\path\to\nupkgs --version ' + $version, + 'dotnet tool install --global CosmosDBShell.osx-x64 --add-source C:\path\to\nupkgs --version ' + $version, + 'dotnet tool install --global CosmosDBShell.osx-arm64 --add-source C:\path\to\nupkgs --version ' + $version, + '```' + ) + $lines -join "`n" | Out-File -FilePath $summary -Encoding utf8 -Append + + - name: Upload branch tool packages + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.version.outputs.artifact_name }} + path: out/nupkg + if-no-files-found: error diff --git a/README.md b/README.md index f619644..dc872f3 100644 --- a/README.md +++ b/README.md @@ -136,10 +136,13 @@ dotnet tool list --global GitHub Actions handles PR validation and unsigned package creation: - [.github/workflows/ci.yml](.github/workflows/ci.yml): restore, build, test, and fuzzer smoke test (runs on every PR) +- [.github/workflows/package-branches.yml](.github/workflows/package-branches.yml): build installable preview NuGet tool packages for every non-`main` branch push and upload the `.nupkg` files as workflow artifacts - [.github/workflows/package-unsigned.yml](.github/workflows/package-unsigned.yml): build and upload unsigned NuGet artifacts (runs on tags) GitHub Actions uses [.github/nuget.github.config](.github/nuget.github.config) so the workflows restore packages from nuget.org without depending on the Azure DevOps feed. +The branch packaging workflow produces preview versions in the form `1.0.-preview.`. Each workflow run also writes a summary with the exact artifact name and ready-to-use `dotnet tool install` commands so the package version is easy to find later. + Azure Pipelines ([.pipelines/CosmosDB-Shell-Official.yml](.pipelines/CosmosDB-Shell-Official.yml)) handles signing and publishing via the internal Azure setup. ## CLI Arguments From 3dae379322e7143a4678dfe4501137bd4c95b83b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Mon, 23 Mar 2026 09:05:27 +0100 Subject: [PATCH 07/30] Update .pipelines/CosmosDB-Shell-Official.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .pipelines/CosmosDB-Shell-Official.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.pipelines/CosmosDB-Shell-Official.yml b/.pipelines/CosmosDB-Shell-Official.yml index aabebcb..561a89a 100644 --- a/.pipelines/CosmosDB-Shell-Official.yml +++ b/.pipelines/CosmosDB-Shell-Official.yml @@ -507,12 +507,7 @@ extends: eq('${{ parameters.publishNuget }}', 'true')) inputs: command: "push" - packagesToPush: | - $(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.win-x64.*.nupkg - $(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.linux-x64.*.nupkg - $(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.linux-arm64.*.nupkg - $(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.osx-x64.*.nupkg - $(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.osx-arm64.*.nupkg + packagesToPush: "$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.win-x64.*.nupkg;$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.linux-x64.*.nupkg;$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.linux-arm64.*.nupkg;$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.osx-x64.*.nupkg;$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.osx-arm64.*.nupkg" nuGetFeedType: "internal" publishVstsFeed: "CosmosDB/CosmosDBShell" allowPackageConflicts: true From 653d3bf707641147bfa72b347a3de2b0e8c6b502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Mon, 23 Mar 2026 09:10:21 +0100 Subject: [PATCH 08/30] Update .github/workflows/package-branches.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/package-branches.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/package-branches.yml b/.github/workflows/package-branches.yml index 37b79f5..4d94205 100644 --- a/.github/workflows/package-branches.yml +++ b/.github/workflows/package-branches.yml @@ -72,6 +72,9 @@ jobs: run: dotnet build CosmosDBShell.sln --configuration $env:BUILD_CONFIGURATION --no-restore shell: pwsh + - name: Test + run: dotnet test CosmosDBShell.Tests/CosmosDBShell.Tests.csproj --configuration $env:BUILD_CONFIGURATION --no-build --no-restore + shell: pwsh - name: Publish runtime artifacts shell: pwsh run: | From 4da789bdc4de26f95d5d97ac447982d3696580aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Mon, 23 Mar 2026 10:11:53 +0100 Subject: [PATCH 09/30] Update .github/workflows/package-branches.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/package-branches.yml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.github/workflows/package-branches.yml b/.github/workflows/package-branches.yml index 4d94205..f5bdcb8 100644 --- a/.github/workflows/package-branches.yml +++ b/.github/workflows/package-branches.yml @@ -75,20 +75,6 @@ jobs: - name: Test run: dotnet test CosmosDBShell.Tests/CosmosDBShell.Tests.csproj --configuration $env:BUILD_CONFIGURATION --no-build --no-restore shell: pwsh - - name: Publish runtime artifacts - shell: pwsh - run: | - $rids = @('win-x64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64') - foreach ($rid in $rids) { - dotnet publish CosmosDBShell/CosmosDBShell.csproj ` - --configuration $env:BUILD_CONFIGURATION ` - --no-restore ` - -r $rid ` - --output "out/$rid" ` - /p:Version=${{ steps.version.outputs.assembly_version }} ` - /p:FileVersion=${{ steps.version.outputs.file_version }} ` - /p:InformationalVersion=${{ steps.version.outputs.informational_version }} - } - name: Pack branch NuGet artifacts shell: pwsh From 599194ec57a92e29590e09e33f91ce97e86012ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Mon, 23 Mar 2026 10:26:20 +0100 Subject: [PATCH 10/30] Add branch package workflow and fix RID packaging --- .github/workflows/package-branches.yml | 20 ++++++++++++++++++++ .github/workflows/package-unsigned.yml | 7 ++++++- CONTRIBUTING.md | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/.github/workflows/package-branches.yml b/.github/workflows/package-branches.yml index f5bdcb8..28d8382 100644 --- a/.github/workflows/package-branches.yml +++ b/.github/workflows/package-branches.yml @@ -76,6 +76,26 @@ jobs: run: dotnet test CosmosDBShell.Tests/CosmosDBShell.Tests.csproj --configuration $env:BUILD_CONFIGURATION --no-build --no-restore shell: pwsh + - name: Publish runtime artifacts + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $rids = @('win-x64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64') + foreach ($rid in $rids) { + dotnet publish CosmosDBShell/CosmosDBShell.csproj ` + --configuration $env:BUILD_CONFIGURATION ` + --configfile .github/nuget.github.config ` + -r $rid ` + --output "out/$rid" ` + /p:Version=${{ steps.version.outputs.assembly_version }} ` + /p:FileVersion=${{ steps.version.outputs.file_version }} ` + /p:InformationalVersion=${{ steps.version.outputs.informational_version }} + + if ($LASTEXITCODE -ne 0) { + throw "RID publish failed for $rid with exit code $LASTEXITCODE." + } + } + - name: Pack branch NuGet artifacts shell: pwsh run: | diff --git a/.github/workflows/package-unsigned.yml b/.github/workflows/package-unsigned.yml index c5f46b0..515a009 100644 --- a/.github/workflows/package-unsigned.yml +++ b/.github/workflows/package-unsigned.yml @@ -107,16 +107,21 @@ jobs: - name: Publish runtime artifacts shell: pwsh run: | + $ErrorActionPreference = 'Stop' $rids = @('win-x64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64') foreach ($rid in $rids) { dotnet publish CosmosDBShell/CosmosDBShell.csproj ` --configuration $env:BUILD_CONFIGURATION ` - --no-restore ` + --configfile .github/nuget.github.config ` -r $rid ` --output "out/$rid" ` /p:Version=${{ steps.version.outputs.package_version }} ` /p:FileVersion=${{ steps.version.outputs.file_version }} ` /p:InformationalVersion=${{ steps.version.outputs.informational_version }} + + if ($LASTEXITCODE -ne 0) { + throw "RID publish failed for $rid with exit code $LASTEXITCODE." + } } - name: Pack unsigned NuGet artifacts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6703b2..d7d2a75 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,7 @@ There are several ways you can contribute to the CosmosDBShell project: - Run the tool locally: `dotnet run --project CosmosDBShell/CosmosDBShell.csproj` - GitHub Actions runs CI from [.github/workflows/ci.yml](.github/workflows/ci.yml) and uploads unsigned artifacts from [.github/workflows/package-unsigned.yml](.github/workflows/package-unsigned.yml). - GitHub Actions uses [.github/nuget.github.config](.github/nuget.github.config) so it can restore from nuget.org independently of Azure Pipelines. - - Azure Pipelines can continue independently for branch-based signing and publishing. + - Azure Pipelines runs from [.pipelines/CosmosDB-Shell-Official.yml](.pipelines/CosmosDB-Shell-Official.yml) for signed builds and publishing from the `main` branch (and any manual runs configured there). This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) From 1ba73f0fe998d941c59bf6924521af5f885773c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:50:35 +0000 Subject: [PATCH 11/30] Initial plan From d588f599c9942a15b90618a5009aaf183c83d629 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:52:51 +0000 Subject: [PATCH 12/30] Fix packagesToPush escaping and add any-RID guard to package-unsigned validation Co-authored-by: mkrueger <341098+mkrueger@users.noreply.github.com> Agent-Logs-Url: https://github.com/Azure/CosmosDBShell/sessions/0e5944c6-6775-4f22-af50-756cb505882f --- .github/workflows/package-unsigned.yml | 7 +++++++ .pipelines/CosmosDB-Shell-Official.yml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/package-unsigned.yml b/.github/workflows/package-unsigned.yml index 515a009..ce2612b 100644 --- a/.github/workflows/package-unsigned.yml +++ b/.github/workflows/package-unsigned.yml @@ -170,6 +170,13 @@ jobs: exit 1 } + $anyMatches = Get-ChildItem -Path (Join-Path $pkgDir 'CosmosDBShell.any.*.nupkg') -ErrorAction SilentlyContinue + if ($anyMatches -and $anyMatches.Count -gt 0) { + $names = $anyMatches | ForEach-Object { $_.Name } | Sort-Object + Write-Error "Unexpected any-RID package(s) found: $($names -join ', ')" + exit 1 + } + - name: Upload test results if: always() uses: actions/upload-artifact@v4 diff --git a/.pipelines/CosmosDB-Shell-Official.yml b/.pipelines/CosmosDB-Shell-Official.yml index 561a89a..35d41e4 100644 --- a/.pipelines/CosmosDB-Shell-Official.yml +++ b/.pipelines/CosmosDB-Shell-Official.yml @@ -518,7 +518,7 @@ extends: eq('${{ parameters.publishNuget }}', 'true')) inputs: command: "push" - packagesToPush: "$(Build.SourcesDirectory)\\out\\nupkg\\CosmosDBShell.$(CosmosDBShell_PackageVersion).nupkg" + packagesToPush: "$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.$(CosmosDBShell_PackageVersion).nupkg" nuGetFeedType: "internal" publishVstsFeed: "CosmosDB/CosmosDBShell" allowPackageConflicts: true From fe0a5254de24b92f2a143322bc6fa2d50b1b7573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Mon, 23 Mar 2026 11:03:44 +0100 Subject: [PATCH 13/30] Fix workflow file. --- .github/workflows/package-branches.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/package-branches.yml b/.github/workflows/package-branches.yml index 28d8382..b6e21d4 100644 --- a/.github/workflows/package-branches.yml +++ b/.github/workflows/package-branches.yml @@ -162,12 +162,13 @@ jobs: $summary = $env:GITHUB_STEP_SUMMARY $version = '${{ steps.version.outputs.package_version }}' $artifactName = '${{ steps.version.outputs.artifact_name }}' + $branchName = '${{ github.ref_name }}' $lines = @( '## Branch tool packages', '', - "- Branch: `${{ github.ref_name }}`", - "- Preview version: `$version`", - "- Artifact: `$artifactName`", + ('- Branch: `' + $branchName + '`'), + ('- Preview version: `' + $version + '`'), + ('- Artifact: `' + $artifactName + '`'), '', 'Download the artifact, extract the `.nupkg` files to a local folder, then install one of these packages:', '', From 494f24af23f4c46c41923a31f59b0d80ac2fc090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Mon, 23 Mar 2026 12:00:45 +0100 Subject: [PATCH 14/30] Split package artifacts by platform in workflows --- .github/workflows/package-branches.yml | 54 +++++++++++++++++++++++--- .github/workflows/package-unsigned.yml | 41 +++++++++++++++++-- 2 files changed, 86 insertions(+), 9 deletions(-) diff --git a/.github/workflows/package-branches.yml b/.github/workflows/package-branches.yml index b6e21d4..10fac3e 100644 --- a/.github/workflows/package-branches.yml +++ b/.github/workflows/package-branches.yml @@ -63,7 +63,7 @@ jobs: "file_version=$fileVersion" >> $env:GITHUB_OUTPUT "informational_version=$infoVersion" >> $env:GITHUB_OUTPUT "branch_label=$branchLabel" >> $env:GITHUB_OUTPUT - "artifact_name=branch-tool-packages-$branchLabel-${{ github.run_number }}" >> $env:GITHUB_OUTPUT + "artifact_suffix=$branchLabel-${{ github.run_number }}" >> $env:GITHUB_OUTPUT - name: Restore run: dotnet restore CosmosDBShell.sln --configfile .github/nuget.github.config @@ -161,14 +161,21 @@ jobs: run: | $summary = $env:GITHUB_STEP_SUMMARY $version = '${{ steps.version.outputs.package_version }}' - $artifactName = '${{ steps.version.outputs.artifact_name }}' + $artifactSuffix = '${{ steps.version.outputs.artifact_suffix }}' $branchName = '${{ github.ref_name }}' $lines = @( '## Branch tool packages', '', ('- Branch: `' + $branchName + '`'), ('- Preview version: `' + $version + '`'), - ('- Artifact: `' + $artifactName + '`'), + '', + 'Artifacts:', + ('- `CosmosDBShell-pointer-' + $artifactSuffix + '`'), + ('- `CosmosDBShell-win-x64-' + $artifactSuffix + '`'), + ('- `CosmosDBShell-linux-x64-' + $artifactSuffix + '`'), + ('- `CosmosDBShell-linux-arm64-' + $artifactSuffix + '`'), + ('- `CosmosDBShell-osx-x64-' + $artifactSuffix + '`'), + ('- `CosmosDBShell-osx-arm64-' + $artifactSuffix + '`'), '', 'Download the artifact, extract the `.nupkg` files to a local folder, then install one of these packages:', '', @@ -183,9 +190,44 @@ jobs: ) $lines -join "`n" | Out-File -FilePath $summary -Encoding utf8 -Append - - name: Upload branch tool packages + - name: Upload pointer package + uses: actions/upload-artifact@v4 + with: + name: CosmosDBShell-pointer-${{ steps.version.outputs.artifact_suffix }} + path: out/nupkg/CosmosDBShell.${{ steps.version.outputs.package_version }}.nupkg + if-no-files-found: error + + - name: Upload win-x64 package + uses: actions/upload-artifact@v4 + with: + name: CosmosDBShell-win-x64-${{ steps.version.outputs.artifact_suffix }} + path: out/nupkg/CosmosDBShell.win-x64.${{ steps.version.outputs.package_version }}.nupkg + if-no-files-found: error + + - name: Upload linux-x64 package + uses: actions/upload-artifact@v4 + with: + name: CosmosDBShell-linux-x64-${{ steps.version.outputs.artifact_suffix }} + path: out/nupkg/CosmosDBShell.linux-x64.${{ steps.version.outputs.package_version }}.nupkg + if-no-files-found: error + + - name: Upload linux-arm64 package + uses: actions/upload-artifact@v4 + with: + name: CosmosDBShell-linux-arm64-${{ steps.version.outputs.artifact_suffix }} + path: out/nupkg/CosmosDBShell.linux-arm64.${{ steps.version.outputs.package_version }}.nupkg + if-no-files-found: error + + - name: Upload osx-x64 package + uses: actions/upload-artifact@v4 + with: + name: CosmosDBShell-osx-x64-${{ steps.version.outputs.artifact_suffix }} + path: out/nupkg/CosmosDBShell.osx-x64.${{ steps.version.outputs.package_version }}.nupkg + if-no-files-found: error + + - name: Upload osx-arm64 package uses: actions/upload-artifact@v4 with: - name: ${{ steps.version.outputs.artifact_name }} - path: out/nupkg + name: CosmosDBShell-osx-arm64-${{ steps.version.outputs.artifact_suffix }} + path: out/nupkg/CosmosDBShell.osx-arm64.${{ steps.version.outputs.package_version }}.nupkg if-no-files-found: error diff --git a/.github/workflows/package-unsigned.yml b/.github/workflows/package-unsigned.yml index ce2612b..629791d 100644 --- a/.github/workflows/package-unsigned.yml +++ b/.github/workflows/package-unsigned.yml @@ -185,9 +185,44 @@ jobs: path: TestResults if-no-files-found: ignore - - name: Upload unsigned artifacts + - name: Upload pointer package uses: actions/upload-artifact@v4 with: - name: unsigned-build-artifacts - path: out + name: CosmosDBShell-pointer-${{ steps.version.outputs.package_version }} + path: out/nupkg/CosmosDBShell.${{ steps.version.outputs.package_version }}.nupkg + if-no-files-found: error + + - name: Upload win-x64 package + uses: actions/upload-artifact@v4 + with: + name: CosmosDBShell-win-x64-${{ steps.version.outputs.package_version }} + path: out/nupkg/CosmosDBShell.win-x64.${{ steps.version.outputs.package_version }}.nupkg + if-no-files-found: error + + - name: Upload linux-x64 package + uses: actions/upload-artifact@v4 + with: + name: CosmosDBShell-linux-x64-${{ steps.version.outputs.package_version }} + path: out/nupkg/CosmosDBShell.linux-x64.${{ steps.version.outputs.package_version }}.nupkg + if-no-files-found: error + + - name: Upload linux-arm64 package + uses: actions/upload-artifact@v4 + with: + name: CosmosDBShell-linux-arm64-${{ steps.version.outputs.package_version }} + path: out/nupkg/CosmosDBShell.linux-arm64.${{ steps.version.outputs.package_version }}.nupkg + if-no-files-found: error + + - name: Upload osx-x64 package + uses: actions/upload-artifact@v4 + with: + name: CosmosDBShell-osx-x64-${{ steps.version.outputs.package_version }} + path: out/nupkg/CosmosDBShell.osx-x64.${{ steps.version.outputs.package_version }}.nupkg + if-no-files-found: error + + - name: Upload osx-arm64 package + uses: actions/upload-artifact@v4 + with: + name: CosmosDBShell-osx-arm64-${{ steps.version.outputs.package_version }} + path: out/nupkg/CosmosDBShell.osx-arm64.${{ steps.version.outputs.package_version }}.nupkg if-no-files-found: error From ce8b13a092829dda601a5662b2a17cf736c8f03b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Mon, 23 Mar 2026 13:12:38 +0100 Subject: [PATCH 15/30] Renamed package file. --- ...anches.yml => package-nuget-artifacts.yml} | 0 .github/workflows/package-unsigned.yml | 228 ------------------ 2 files changed, 228 deletions(-) rename .github/workflows/{package-branches.yml => package-nuget-artifacts.yml} (100%) delete mode 100644 .github/workflows/package-unsigned.yml diff --git a/.github/workflows/package-branches.yml b/.github/workflows/package-nuget-artifacts.yml similarity index 100% rename from .github/workflows/package-branches.yml rename to .github/workflows/package-nuget-artifacts.yml diff --git a/.github/workflows/package-unsigned.yml b/.github/workflows/package-unsigned.yml deleted file mode 100644 index 629791d..0000000 --- a/.github/workflows/package-unsigned.yml +++ /dev/null @@ -1,228 +0,0 @@ -name: Package Unsigned Artifacts - -on: - workflow_dispatch: - push: - tags: - - v* - -concurrency: - group: package-unsigned-${{ github.ref }} - cancel-in-progress: false - -permissions: - contents: read - -env: - BUILD_CONFIGURATION: Release - -jobs: - package: - runs-on: windows-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v4 - with: - global-json-file: global.json - cache: true - cache-dependency-path: | - global.json - Directory.Packages.props - **/*.csproj - - - name: Compute version properties - id: version - shell: pwsh - run: | - $runNumber = [int]"${{ github.run_number }}" - $version = "1.0.$runNumber" - - if ("${{ github.ref_type }}" -eq "tag") { - $candidateVersion = "${{ github.ref_name }}" - if ($candidateVersion.StartsWith('v')) { - $candidateVersion = $candidateVersion.Substring(1) - } - - # Accept numeric SemVer: major.minor.patch with optional prerelease/build metadata - if ($candidateVersion -match '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$') { - $version = $candidateVersion - } - } - - # Derive a strictly numeric file version (major.minor.build.revision) - $numericVersion = $version.Split('+')[0].Split('-')[0] - $parts = $numericVersion.Split('.') - - $allNumeric = $true - foreach ($p in $parts) { - $tmp = 0 - if (-not [int]::TryParse($p, [ref]$tmp)) { - $allNumeric = $false - break - } - } - - if (-not $allNumeric -or $parts.Count -lt 1) { - $fileVersion = "1.0.$runNumber.0" - } else { - $major = [int]$parts[0] - $minor = if ($parts.Count -ge 2) { [int]$parts[1] } else { 0 } - $build = if ($parts.Count -ge 3) { [int]$parts[2] } else { $runNumber } - $revision = if ($parts.Count -ge 4) { [int]$parts[3] } else { 0 } - $fileVersion = "$major.$minor.$build.$revision" - } - - if ($version -match '\+') { - $infoVersion = "$version.${{ github.sha }}" - } else { - $infoVersion = "$version+${{ github.sha }}" - } - - "package_version=$version" >> $env:GITHUB_OUTPUT - "file_version=$fileVersion" >> $env:GITHUB_OUTPUT - "informational_version=$infoVersion" >> $env:GITHUB_OUTPUT - - - name: Restore - run: dotnet restore CosmosDBShell.sln --configfile .github/nuget.github.config - - - name: Build solution - run: dotnet build CosmosDBShell.sln --configuration $env:BUILD_CONFIGURATION --no-restore - shell: pwsh - - - name: Test solution - run: >- - dotnet test CosmosDBShell.sln - --configuration $env:BUILD_CONFIGURATION - --no-build - --no-restore - --logger "trx;LogFileName=test-results.trx" - --results-directory TestResults - --collect "Code coverage" - shell: pwsh - - - name: Publish runtime artifacts - shell: pwsh - run: | - $ErrorActionPreference = 'Stop' - $rids = @('win-x64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64') - foreach ($rid in $rids) { - dotnet publish CosmosDBShell/CosmosDBShell.csproj ` - --configuration $env:BUILD_CONFIGURATION ` - --configfile .github/nuget.github.config ` - -r $rid ` - --output "out/$rid" ` - /p:Version=${{ steps.version.outputs.package_version }} ` - /p:FileVersion=${{ steps.version.outputs.file_version }} ` - /p:InformationalVersion=${{ steps.version.outputs.informational_version }} - - if ($LASTEXITCODE -ne 0) { - throw "RID publish failed for $rid with exit code $LASTEXITCODE." - } - } - - - name: Pack unsigned NuGet artifacts - shell: pwsh - run: | - New-Item -ItemType Directory -Path out/nupkg -Force | Out-Null - dotnet pack CosmosDBShell/CosmosDBShell.csproj ` - --configuration $env:BUILD_CONFIGURATION ` - --no-build ` - --no-restore ` - --output out/nupkg ` - /p:PackageVersion=${{ steps.version.outputs.package_version }} ` - /p:Version=${{ steps.version.outputs.package_version }} ` - /p:FileVersion=${{ steps.version.outputs.file_version }} ` - /p:InformationalVersion=${{ steps.version.outputs.informational_version }} ` - /p:ContinuousIntegrationBuild=true - - - name: Validate NuGet package set - shell: pwsh - run: | - $pkgDir = Join-Path $pwd 'out/nupkg' - $ridPatterns = @( - 'CosmosDBShell.win-x64.*.nupkg', - 'CosmosDBShell.linux-x64.*.nupkg', - 'CosmosDBShell.linux-arm64.*.nupkg', - 'CosmosDBShell.osx-x64.*.nupkg', - 'CosmosDBShell.osx-arm64.*.nupkg' - ) - - foreach ($pattern in $ridPatterns) { - $matches = Get-ChildItem -Path (Join-Path $pkgDir $pattern) -ErrorAction SilentlyContinue - if (-not $matches -or $matches.Count -eq 0) { - Write-Error "Expected package was not generated: $pattern" - exit 1 - } - } - - $allPackages = Get-ChildItem -Path (Join-Path $pkgDir 'CosmosDBShell.*.nupkg') -ErrorAction SilentlyContinue - $pointerPackages = $allPackages | Where-Object { - $_.Name -notmatch '^CosmosDBShell\.(win-x64|linux-x64|linux-arm64|osx-x64|osx-arm64)\..+\.nupkg$' - } - - if (-not $pointerPackages -or $pointerPackages.Count -ne 1) { - $names = @($pointerPackages | ForEach-Object { $_.Name }) - Write-Error "Expected exactly one pointer package (non-RID). Found: $($names -join ', ')" - exit 1 - } - - $anyMatches = Get-ChildItem -Path (Join-Path $pkgDir 'CosmosDBShell.any.*.nupkg') -ErrorAction SilentlyContinue - if ($anyMatches -and $anyMatches.Count -gt 0) { - $names = $anyMatches | ForEach-Object { $_.Name } | Sort-Object - Write-Error "Unexpected any-RID package(s) found: $($names -join ', ')" - exit 1 - } - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v4 - with: - name: test-results - path: TestResults - if-no-files-found: ignore - - - name: Upload pointer package - uses: actions/upload-artifact@v4 - with: - name: CosmosDBShell-pointer-${{ steps.version.outputs.package_version }} - path: out/nupkg/CosmosDBShell.${{ steps.version.outputs.package_version }}.nupkg - if-no-files-found: error - - - name: Upload win-x64 package - uses: actions/upload-artifact@v4 - with: - name: CosmosDBShell-win-x64-${{ steps.version.outputs.package_version }} - path: out/nupkg/CosmosDBShell.win-x64.${{ steps.version.outputs.package_version }}.nupkg - if-no-files-found: error - - - name: Upload linux-x64 package - uses: actions/upload-artifact@v4 - with: - name: CosmosDBShell-linux-x64-${{ steps.version.outputs.package_version }} - path: out/nupkg/CosmosDBShell.linux-x64.${{ steps.version.outputs.package_version }}.nupkg - if-no-files-found: error - - - name: Upload linux-arm64 package - uses: actions/upload-artifact@v4 - with: - name: CosmosDBShell-linux-arm64-${{ steps.version.outputs.package_version }} - path: out/nupkg/CosmosDBShell.linux-arm64.${{ steps.version.outputs.package_version }}.nupkg - if-no-files-found: error - - - name: Upload osx-x64 package - uses: actions/upload-artifact@v4 - with: - name: CosmosDBShell-osx-x64-${{ steps.version.outputs.package_version }} - path: out/nupkg/CosmosDBShell.osx-x64.${{ steps.version.outputs.package_version }}.nupkg - if-no-files-found: error - - - name: Upload osx-arm64 package - uses: actions/upload-artifact@v4 - with: - name: CosmosDBShell-osx-arm64-${{ steps.version.outputs.package_version }} - path: out/nupkg/CosmosDBShell.osx-arm64.${{ steps.version.outputs.package_version }}.nupkg - if-no-files-found: error From bf171ea0e87279908dabc3244d282763c9d862eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Mon, 23 Mar 2026 13:22:01 +0100 Subject: [PATCH 16/30] Package now runs on all branches. --- .github/workflows/package-nuget-artifacts.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/package-nuget-artifacts.yml b/.github/workflows/package-nuget-artifacts.yml index 10fac3e..c085355 100644 --- a/.github/workflows/package-nuget-artifacts.yml +++ b/.github/workflows/package-nuget-artifacts.yml @@ -3,8 +3,8 @@ name: Package Branch Tool Artifacts on: workflow_dispatch: push: - branches-ignore: - - main + branches: + - "**" concurrency: group: package-branches-${{ github.ref }} From 44dde5a4a124c2ad60ff4620e5843f32e1656da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Mon, 23 Mar 2026 13:49:03 +0100 Subject: [PATCH 17/30] Fixed version update in build. + Renamed & joined workflow file. --- .github/workflows/ci.yml | 84 ------------------- ...artifacts.yml => validate-and-package.yml} | 84 +++++++++++++++++-- CONTRIBUTING.md | 2 +- .../ShellInterpreter.cs | 2 +- README.md | 8 +- 5 files changed, 81 insertions(+), 99 deletions(-) delete mode 100644 .github/workflows/ci.yml rename .github/workflows/{package-nuget-artifacts.yml => validate-and-package.yml} (78%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index d224759..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: CI - -on: - pull_request: - push: - branches: - - main - workflow_dispatch: - -concurrency: - group: ci-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -env: - BUILD_CONFIGURATION: Release - -jobs: - build-test: - runs-on: windows-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v4 - with: - global-json-file: global.json - cache: true - cache-dependency-path: | - global.json - Directory.Packages.props - **/*.csproj - - - name: Restore - run: dotnet restore CosmosDBShell.sln --configfile .github/nuget.github.config - - - name: Build solution - run: dotnet build CosmosDBShell.sln --configuration $env:BUILD_CONFIGURATION --no-restore - shell: pwsh - - - name: Test solution - run: >- - dotnet test CosmosDBShell.sln - --configuration $env:BUILD_CONFIGURATION - --no-build - --no-restore - --logger "trx;LogFileName=test-results.trx" - --results-directory TestResults - --collect "Code coverage" - shell: pwsh - - - name: Run fuzzer smoke test - working-directory: CosmosDBShell.Fuzzer - run: dotnet run --configuration $env:BUILD_CONFIGURATION --no-build --no-restore -- --all - shell: pwsh - - - name: Fail if fuzzer crash findings exist - shell: pwsh - run: | - $crashes = Get-ChildItem -Path CosmosDBShell.Fuzzer/findings -Filter 'crash_*.txt' -ErrorAction SilentlyContinue - if ($crashes -and $crashes.Count -gt 0) { - Write-Error "Fuzzer recorded $($crashes.Count) crash(es). See the 'fuzz-findings' artifact for details." - exit 1 - } - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v4 - with: - name: test-results - path: TestResults - if-no-files-found: ignore - - - name: Upload fuzzer findings - if: always() - uses: actions/upload-artifact@v4 - with: - name: fuzz-findings - path: CosmosDBShell.Fuzzer/findings - if-no-files-found: ignore diff --git a/.github/workflows/package-nuget-artifacts.yml b/.github/workflows/validate-and-package.yml similarity index 78% rename from .github/workflows/package-nuget-artifacts.yml rename to .github/workflows/validate-and-package.yml index c085355..0d5cbed 100644 --- a/.github/workflows/package-nuget-artifacts.yml +++ b/.github/workflows/validate-and-package.yml @@ -1,14 +1,15 @@ -name: Package Branch Tool Artifacts +name: Validate And Package on: - workflow_dispatch: + pull_request: push: branches: - "**" + workflow_dispatch: concurrency: - group: package-branches-${{ github.ref }} - cancel-in-progress: false + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true permissions: contents: read @@ -17,7 +18,74 @@ env: BUILD_CONFIGURATION: Release jobs: - package: + build-test: + if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + cache: true + cache-dependency-path: | + global.json + Directory.Packages.props + **/*.csproj + + - name: Restore + run: dotnet restore CosmosDBShell.sln --configfile .github/nuget.github.config + + - name: Build solution + run: dotnet build CosmosDBShell.sln --configuration $env:BUILD_CONFIGURATION --no-restore + shell: pwsh + + - name: Test solution + run: >- + dotnet test CosmosDBShell.sln + --configuration $env:BUILD_CONFIGURATION + --no-build + --no-restore + --logger "trx;LogFileName=test-results.trx" + --results-directory TestResults + --collect "Code coverage" + shell: pwsh + + - name: Run fuzzer smoke test + working-directory: CosmosDBShell.Fuzzer + run: dotnet run --configuration $env:BUILD_CONFIGURATION --no-build --no-restore -- --all + shell: pwsh + + - name: Fail if fuzzer crash findings exist + shell: pwsh + run: | + $crashes = Get-ChildItem -Path CosmosDBShell.Fuzzer/findings -Filter 'crash_*.txt' -ErrorAction SilentlyContinue + if ($crashes -and $crashes.Count -gt 0) { + Write-Error "Fuzzer recorded $($crashes.Count) crash(es). See the 'fuzz-findings' artifact for details." + exit 1 + } + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: TestResults + if-no-files-found: ignore + + - name: Upload fuzzer findings + if: always() + uses: actions/upload-artifact@v4 + with: + name: fuzz-findings + path: CosmosDBShell.Fuzzer/findings + if-no-files-found: ignore + + package-nuget: + if: github.event_name != 'pull_request' runs-on: windows-latest steps: @@ -96,7 +164,7 @@ jobs: } } - - name: Pack branch NuGet artifacts + - name: Pack NuGet artifacts shell: pwsh run: | New-Item -ItemType Directory -Path out/nupkg -Force | Out-Null @@ -164,7 +232,7 @@ jobs: $artifactSuffix = '${{ steps.version.outputs.artifact_suffix }}' $branchName = '${{ github.ref_name }}' $lines = @( - '## Branch tool packages', + '## NuGet packages', '', ('- Branch: `' + $branchName + '`'), ('- Preview version: `' + $version + '`'), @@ -177,7 +245,7 @@ jobs: ('- `CosmosDBShell-osx-x64-' + $artifactSuffix + '`'), ('- `CosmosDBShell-osx-arm64-' + $artifactSuffix + '`'), '', - 'Download the artifact, extract the `.nupkg` files to a local folder, then install one of these packages:', + 'Download the artifact, extract the `.nupkg` file to a local folder, then install one of these packages:', '', '```powershell', 'dotnet tool install --global CosmosDBShell --add-source C:\path\to\nupkgs --version ' + $version, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d7d2a75..722b0d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ There are several ways you can contribute to the CosmosDBShell project: - Build: `dotnet build CosmosDBShell.sln` (or use the VS Code build task with Ctrl+Shift+B). - Run tests: `dotnet test CosmosDBShell.sln` - Run the tool locally: `dotnet run --project CosmosDBShell/CosmosDBShell.csproj` - - GitHub Actions runs CI from [.github/workflows/ci.yml](.github/workflows/ci.yml) and uploads unsigned artifacts from [.github/workflows/package-unsigned.yml](.github/workflows/package-unsigned.yml). + - GitHub Actions runs CI and uploads NuGet package artifacts from [.github/workflows/validate-and-package.yml](.github/workflows/validate-and-package.yml). - GitHub Actions uses [.github/nuget.github.config](.github/nuget.github.config) so it can restore from nuget.org independently of Azure Pipelines. - Azure Pipelines runs from [.pipelines/CosmosDB-Shell-Official.yml](.pipelines/CosmosDB-Shell-Official.yml) for signed builds and publishing from the `main` branch (and any manual runs configured there). diff --git a/CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs b/CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs index cc416b4..271b5f2 100644 --- a/CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs +++ b/CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs @@ -304,7 +304,7 @@ internal static string GetDisplayVersion(Assembly assembly) var informationalVersion = assembly.GetCustomAttribute()?.InformationalVersion; if (!string.IsNullOrWhiteSpace(informationalVersion)) { - return informationalVersion; + return informationalVersion.Split('+')[0]; } return assembly.GetName().Version?.ToString() ?? "unknown"; diff --git a/README.md b/README.md index dc872f3..f332571 100644 --- a/README.md +++ b/README.md @@ -133,15 +133,13 @@ dotnet tool list --global ## CI And Packaging -GitHub Actions handles PR validation and unsigned package creation: +GitHub Actions uses a single workflow for validation and branch/main package artifacts: -- [.github/workflows/ci.yml](.github/workflows/ci.yml): restore, build, test, and fuzzer smoke test (runs on every PR) -- [.github/workflows/package-branches.yml](.github/workflows/package-branches.yml): build installable preview NuGet tool packages for every non-`main` branch push and upload the `.nupkg` files as workflow artifacts -- [.github/workflows/package-unsigned.yml](.github/workflows/package-unsigned.yml): build and upload unsigned NuGet artifacts (runs on tags) +- [.github/workflows/validate-and-package.yml](.github/workflows/validate-and-package.yml): runs PR validation and uploads installable NuGet tool packages as artifacts on branch and main pushes GitHub Actions uses [.github/nuget.github.config](.github/nuget.github.config) so the workflows restore packages from nuget.org without depending on the Azure DevOps feed. -The branch packaging workflow produces preview versions in the form `1.0.-preview.`. Each workflow run also writes a summary with the exact artifact name and ready-to-use `dotnet tool install` commands so the package version is easy to find later. +The packaging job produces preview versions in the form `1.0.-preview.`, uploads separate artifacts for the pointer package and each RID-specific package, and writes a summary with the artifact names plus ready-to-use `dotnet tool install` commands. Azure Pipelines ([.pipelines/CosmosDB-Shell-Official.yml](.pipelines/CosmosDB-Shell-Official.yml)) handles signing and publishing via the internal Azure setup. From a8cc7547fc21eb2cd2ac7f08aa1f760fe3a0c1e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Tue, 24 Mar 2026 09:14:23 +0100 Subject: [PATCH 18/30] Consolidate CI build/test and packaging into one workflow job --- .github/workflows/validate-and-package.yml | 45 +++++++--------------- README.md | 14 ++----- 2 files changed, 17 insertions(+), 42 deletions(-) diff --git a/.github/workflows/validate-and-package.yml b/.github/workflows/validate-and-package.yml index 0d5cbed..0c6b2e5 100644 --- a/.github/workflows/validate-and-package.yml +++ b/.github/workflows/validate-and-package.yml @@ -18,8 +18,8 @@ env: BUILD_CONFIGURATION: Release jobs: - build-test: - if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' + build-test-package: + if: github.event_name == 'pull_request' || startsWith(github.ref, 'refs/heads/') || github.event_name == 'workflow_dispatch' runs-on: windows-latest steps: @@ -84,25 +84,8 @@ jobs: path: CosmosDBShell.Fuzzer/findings if-no-files-found: ignore - package-nuget: - if: github.event_name != 'pull_request' - runs-on: windows-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v4 - with: - global-json-file: global.json - cache: true - cache-dependency-path: | - global.json - Directory.Packages.props - **/*.csproj - - name: Compute version properties + if: github.event_name != 'pull_request' id: version shell: pwsh run: | @@ -133,18 +116,8 @@ jobs: "branch_label=$branchLabel" >> $env:GITHUB_OUTPUT "artifact_suffix=$branchLabel-${{ github.run_number }}" >> $env:GITHUB_OUTPUT - - name: Restore - run: dotnet restore CosmosDBShell.sln --configfile .github/nuget.github.config - - - name: Build solution - run: dotnet build CosmosDBShell.sln --configuration $env:BUILD_CONFIGURATION --no-restore - shell: pwsh - - - name: Test - run: dotnet test CosmosDBShell.Tests/CosmosDBShell.Tests.csproj --configuration $env:BUILD_CONFIGURATION --no-build --no-restore - shell: pwsh - - name: Publish runtime artifacts + if: github.event_name != 'pull_request' shell: pwsh run: | $ErrorActionPreference = 'Stop' @@ -165,6 +138,7 @@ jobs: } - name: Pack NuGet artifacts + if: github.event_name != 'pull_request' shell: pwsh run: | New-Item -ItemType Directory -Path out/nupkg -Force | Out-Null @@ -180,6 +154,7 @@ jobs: /p:ContinuousIntegrationBuild=true - name: Validate NuGet package set + if: github.event_name != 'pull_request' shell: pwsh run: | $pkgDir = Join-Path $pwd 'out/nupkg' @@ -218,6 +193,7 @@ jobs: } - name: List packaged NuGet files + if: github.event_name != 'pull_request' shell: pwsh run: | Get-ChildItem -Path out/nupkg -Filter *.nupkg -File | Sort-Object Name | ForEach-Object { @@ -225,6 +201,7 @@ jobs: } - name: Write package install summary + if: github.event_name != 'pull_request' shell: pwsh run: | $summary = $env:GITHUB_STEP_SUMMARY @@ -259,6 +236,7 @@ jobs: $lines -join "`n" | Out-File -FilePath $summary -Encoding utf8 -Append - name: Upload pointer package + if: github.event_name != 'pull_request' uses: actions/upload-artifact@v4 with: name: CosmosDBShell-pointer-${{ steps.version.outputs.artifact_suffix }} @@ -266,6 +244,7 @@ jobs: if-no-files-found: error - name: Upload win-x64 package + if: github.event_name != 'pull_request' uses: actions/upload-artifact@v4 with: name: CosmosDBShell-win-x64-${{ steps.version.outputs.artifact_suffix }} @@ -273,6 +252,7 @@ jobs: if-no-files-found: error - name: Upload linux-x64 package + if: github.event_name != 'pull_request' uses: actions/upload-artifact@v4 with: name: CosmosDBShell-linux-x64-${{ steps.version.outputs.artifact_suffix }} @@ -280,6 +260,7 @@ jobs: if-no-files-found: error - name: Upload linux-arm64 package + if: github.event_name != 'pull_request' uses: actions/upload-artifact@v4 with: name: CosmosDBShell-linux-arm64-${{ steps.version.outputs.artifact_suffix }} @@ -287,6 +268,7 @@ jobs: if-no-files-found: error - name: Upload osx-x64 package + if: github.event_name != 'pull_request' uses: actions/upload-artifact@v4 with: name: CosmosDBShell-osx-x64-${{ steps.version.outputs.artifact_suffix }} @@ -294,6 +276,7 @@ jobs: if-no-files-found: error - name: Upload osx-arm64 package + if: github.event_name != 'pull_request' uses: actions/upload-artifact@v4 with: name: CosmosDBShell-osx-arm64-${{ steps.version.outputs.artifact_suffix }} diff --git a/README.md b/README.md index f332571..175b5da 100644 --- a/README.md +++ b/README.md @@ -117,12 +117,6 @@ If you installed the base package instead: dotnet tool uninstall --global CosmosDBShell ``` -If you are not sure which package ID is installed, list global tools first: - -```bash -dotnet tool list --global -``` - ## Documentation - [Connection](docs/connect.md) - Authentication and connection options @@ -137,13 +131,11 @@ GitHub Actions uses a single workflow for validation and branch/main package art - [.github/workflows/validate-and-package.yml](.github/workflows/validate-and-package.yml): runs PR validation and uploads installable NuGet tool packages as artifacts on branch and main pushes -GitHub Actions uses [.github/nuget.github.config](.github/nuget.github.config) so the workflows restore packages from nuget.org without depending on the Azure DevOps feed. - -The packaging job produces preview versions in the form `1.0.-preview.`, uploads separate artifacts for the pointer package and each RID-specific package, and writes a summary with the artifact names plus ready-to-use `dotnet tool install` commands. +GitHub Actions uses [.github/nuget.github.config](.github/nuget.github.config) so the workflow restores packages from nuget.org without depending on the Azure DevOps feed. -Azure Pipelines ([.pipelines/CosmosDB-Shell-Official.yml](.pipelines/CosmosDB-Shell-Official.yml)) handles signing and publishing via the internal Azure setup. +The packaging workflow produces preview versions in the form `1.0.-preview.`. Each workflow run also writes a summary with the artifact names and install commands. -## CLI Arguments +## Command-Line Arguments | Option | Description | | ------ | ----------- | From f02d27cd4cc882da215239b485b66685003b0df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Wed, 25 Mar 2026 12:02:52 +0100 Subject: [PATCH 19/30] Fix pipeline. --- .pipelines/CosmosDB-Shell-Official.yml | 90 +++++++++++++++++--------- 1 file changed, 61 insertions(+), 29 deletions(-) diff --git a/.pipelines/CosmosDB-Shell-Official.yml b/.pipelines/CosmosDB-Shell-Official.yml index 35d41e4..9ac2fd7 100644 --- a/.pipelines/CosmosDB-Shell-Official.yml +++ b/.pipelines/CosmosDB-Shell-Official.yml @@ -433,9 +433,9 @@ extends: $_.Name -notmatch '^CosmosDBShell\.(win-x64|linux-x64|linux-arm64|osx-x64|osx-arm64)\..+\.nupkg$' } - if (-not $pointerPackages -or $pointerPackages.Count -ne 1) { + if ($pointerPackages -and $pointerPackages.Count -gt 1) { $names = @($pointerPackages | ForEach-Object { $_.Name }) - Write-Error "Expected exactly one pointer package (non-RID). Found: $($names -join ', ')" + Write-Error "Expected at most one pointer package (non-RID). Found: $($names -join ', ')" exit 1 } @@ -446,6 +446,17 @@ extends: exit 1 } + - task: PowerShell@2 + displayName: "Remove pointer package from shipping outputs" + condition: succeeded() + inputs: + targetType: inline + script: | + $pkgDir = "$(Build.SourcesDirectory)\out\nupkg" + Get-ChildItem -Path (Join-Path $pkgDir "CosmosDBShell.*.nupkg") -ErrorAction SilentlyContinue | Where-Object { + $_.Name -notmatch '^CosmosDBShell\.(win-x64|linux-x64|linux-arm64|osx-x64|osx-arm64)\..+\.nupkg$' + } | Remove-Item -Force + - task: onebranch.pipeline.signing@1 displayName: "Sign NuGet packages" condition: succeeded() @@ -484,10 +495,9 @@ extends: } Write-Host "Non-shipping build outputs cleaned." - # Push RID-specific NuGet packages first, then the pointer package last. - # With ToolPackageRuntimeIdentifiers, dotnet pack produces per-RID packages - # (e.g. CosmosDBShell.win-x64.*.nupkg) plus a pointer package (CosmosDBShell.*.nupkg). - # All RID packages must be available before the pointer package is published. + # Publish only RID-specific NuGet tool packages. + # dotnet pack may also generate a non-RID pointer package, but feed installs + # should use the platform-specific package IDs directly. - task: PowerShell@2 displayName: "List NuGet packages before publish" condition: succeeded() @@ -499,29 +509,51 @@ extends: Get-ChildItem -Path $pkgDir -Filter *.nupkg -File | Sort-Object Name | ForEach-Object { Write-Host " - $($_.Name) [$($_.Length) bytes]" } - - - task: NuGetCommand@2 - displayName: "Push RID-specific NuGet packages" - condition: and(succeeded(), - eq(variables['Build.SourceBranch'], 'refs/heads/main'), - eq('${{ parameters.publishNuget }}', 'true')) - inputs: - command: "push" - packagesToPush: "$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.win-x64.*.nupkg;$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.linux-x64.*.nupkg;$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.linux-arm64.*.nupkg;$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.osx-x64.*.nupkg;$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.osx-arm64.*.nupkg" - nuGetFeedType: "internal" - publishVstsFeed: "CosmosDB/CosmosDBShell" - allowPackageConflicts: true - - task: NuGetCommand@2 - displayName: "Push pointer NuGet package" - condition: and(succeeded(), - eq(variables['Build.SourceBranch'], 'refs/heads/main'), - eq('${{ parameters.publishNuget }}', 'true')) - inputs: - command: "push" - packagesToPush: "$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.$(CosmosDBShell_PackageVersion).nupkg" - nuGetFeedType: "internal" - publishVstsFeed: "CosmosDB/CosmosDBShell" - allowPackageConflicts: true + - ${{ if and(eq(variables['Build.SourceBranch'], 'refs/heads/main'), eq(parameters.publishNuget, true)) }}: + - task: NuGetCommand@2 + displayName: "Push win-x64 NuGet package" + inputs: + command: "push" + packagesToPush: '$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.win-x64.*.nupkg' + nuGetFeedType: "internal" + publishVstsFeed: "CosmosDB/CosmosDBShell" + allowPackageConflicts: true + + - task: NuGetCommand@2 + displayName: "Push linux-x64 NuGet package" + inputs: + command: "push" + packagesToPush: '$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.linux-x64.*.nupkg' + nuGetFeedType: "internal" + publishVstsFeed: "CosmosDB/CosmosDBShell" + allowPackageConflicts: true + + - task: NuGetCommand@2 + displayName: "Push linux-arm64 NuGet package" + inputs: + command: "push" + packagesToPush: '$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.linux-arm64.*.nupkg' + nuGetFeedType: "internal" + publishVstsFeed: "CosmosDB/CosmosDBShell" + allowPackageConflicts: true + + - task: NuGetCommand@2 + displayName: "Push osx-x64 NuGet package" + inputs: + command: "push" + packagesToPush: '$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.osx-x64.*.nupkg' + nuGetFeedType: "internal" + publishVstsFeed: "CosmosDB/CosmosDBShell" + allowPackageConflicts: true + + - task: NuGetCommand@2 + displayName: "Push osx-arm64 NuGet package" + inputs: + command: "push" + packagesToPush: '$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.osx-arm64.*.nupkg' + nuGetFeedType: "internal" + publishVstsFeed: "CosmosDB/CosmosDBShell" + allowPackageConflicts: true - job: CodeQLAnalyze displayName: CodeQL (C#) From 128f116d7f0e3d6aef4e6e9456e9184ea741bb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Fri, 27 Mar 2026 09:33:44 +0100 Subject: [PATCH 20/30] Bump required .NET to 10. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 175b5da..9713c8c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Lightweight CLI for Azure Cosmos DB. ## Quick Start -**Requirements:** .NET SDK 9.0+ +**Requirements:** .NET SDK 10.0+ ```bash dotnet run --project CosmosDBShell @@ -34,6 +34,8 @@ query "SELECT * FROM c" When consuming build artifacts (`*.nupkg`) from this repo, install as a .NET global tool. +`dotnet tool install` for these packages requires .NET 10 because the tool packages target `net10.0`. + 1. Download the NuGet package(s) to a local folder. 2. Install from that folder with `--add-source`. From 4e74a6e166e6174e6f4a6faa02c5a91c65499a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Fri, 27 Mar 2026 11:48:41 +0100 Subject: [PATCH 21/30] Align workflow docs and package version metadata - dedupe the README CI/package workflow section - remove stale workflow references and broken leftover table rows - base official pipeline InformationalVersion on PackageVersion --- .pipelines/CosmosDB-Shell-Official.yml | 2 +- README.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.pipelines/CosmosDB-Shell-Official.yml b/.pipelines/CosmosDB-Shell-Official.yml index 9ac2fd7..ae4f458 100644 --- a/.pipelines/CosmosDB-Shell-Official.yml +++ b/.pipelines/CosmosDB-Shell-Official.yml @@ -141,7 +141,7 @@ extends: $fileVersion = ($parts[0..3] -join '.') } - $infoVersion = "$buildNumber+$(Build.SourceVersion)" + $infoVersion = "$packageVersion+$(Build.SourceVersion)" Write-Host "##vso[task.setvariable variable=CosmosDBShell_Version]$version" Write-Host "##vso[task.setvariable variable=CosmosDBShell_PackageVersion]$packageVersion" diff --git a/README.md b/README.md index 9713c8c..9841e63 100644 --- a/README.md +++ b/README.md @@ -129,13 +129,13 @@ dotnet tool uninstall --global CosmosDBShell ## CI And Packaging -GitHub Actions uses a single workflow for validation and branch/main package artifacts: +This repo currently uses one GitHub Actions workflow for validation and package artifacts: -- [.github/workflows/validate-and-package.yml](.github/workflows/validate-and-package.yml): runs PR validation and uploads installable NuGet tool packages as artifacts on branch and main pushes +- [.github/workflows/validate-and-package.yml](.github/workflows/validate-and-package.yml): runs validation on pull requests, and on branch pushes or manual runs it also builds installable RID-specific NuGet tool packages and uploads them as workflow artifacts -GitHub Actions uses [.github/nuget.github.config](.github/nuget.github.config) so the workflow restores packages from nuget.org without depending on the Azure DevOps feed. +GitHub Actions uses [.github/nuget.github.config](.github/nuget.github.config) so restores do not depend on the Azure DevOps feed. -The packaging workflow produces preview versions in the form `1.0.-preview.`. Each workflow run also writes a summary with the artifact names and install commands. +Packaging runs produce preview versions in the form `1.0.-preview.`, upload separate artifacts for each RID-specific package, and write a workflow summary with artifact names plus ready-to-use `dotnet tool install` commands. ## Command-Line Arguments From c50e7dee9831660e4fa27798906fed7b2b62e0ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Thu, 16 Apr 2026 13:07:27 +0200 Subject: [PATCH 22/30] ci: address workflow review comments --- .github/workflows/validate-and-package.yml | 1 + .pipelines/CosmosDB-Shell-Official.yml | 29 +++++++++++++++------- README.md | 2 +- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/.github/workflows/validate-and-package.yml b/.github/workflows/validate-and-package.yml index 0c6b2e5..4be7fc4 100644 --- a/.github/workflows/validate-and-package.yml +++ b/.github/workflows/validate-and-package.yml @@ -126,6 +126,7 @@ jobs: dotnet publish CosmosDBShell/CosmosDBShell.csproj ` --configuration $env:BUILD_CONFIGURATION ` --configfile .github/nuget.github.config ` + --no-restore ` -r $rid ` --output "out/$rid" ` /p:Version=${{ steps.version.outputs.assembly_version }} ` diff --git a/.pipelines/CosmosDB-Shell-Official.yml b/.pipelines/CosmosDB-Shell-Official.yml index ae4f458..d901e17 100644 --- a/.pipelines/CosmosDB-Shell-Official.yml +++ b/.pipelines/CosmosDB-Shell-Official.yml @@ -324,18 +324,29 @@ extends: # EXE that bundles all assemblies — the input signatures are lost. # The actual payload signing happens post-pack via extract/sign/repack below. - - task: DotNetCoreCLI@2 + - task: PowerShell@2 displayName: "Pack CosmosDBShell NuGet" condition: succeeded() inputs: - command: "pack" - packagesToPack: "$(ReleaseProject)" - configurationToPack: "$(BuildConfiguration)" - nobuild: true - outputDir: '$(Build.SourcesDirectory)\out\nupkg' - versioningScheme: byEnvVar - versionEnvVar: CosmosDBShell_PackageVersion - buildProperties: "Version=$(CosmosDBShell_Version);PackageVersion=$(CosmosDBShell_PackageVersion);FileVersion=$(CosmosDBShell_FileVersion);InformationalVersion=$(CosmosDBShell_InformationalVersion);ContinuousIntegrationBuild=true" + targetType: inline + script: | + $pkgDir = "$(Build.SourcesDirectory)\out\nupkg" + New-Item -ItemType Directory -Path $pkgDir -Force | Out-Null + + dotnet pack "$(ReleaseProject)" ` + --configuration "$(BuildConfiguration)" ` + --no-build ` + --no-restore ` + --output $pkgDir ` + /p:Version=$(CosmosDBShell_Version) ` + /p:PackageVersion=$(CosmosDBShell_PackageVersion) ` + /p:FileVersion=$(CosmosDBShell_FileVersion) ` + /p:InformationalVersion=$(CosmosDBShell_InformationalVersion) ` + /p:ContinuousIntegrationBuild=true + + if ($LASTEXITCODE -ne 0) { + throw "dotnet pack failed with exit code $LASTEXITCODE." + } - task: PowerShell@2 displayName: "Expand RID packages for payload signing" diff --git a/README.md b/README.md index 9841e63..61e2108 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ This repo currently uses one GitHub Actions workflow for validation and package GitHub Actions uses [.github/nuget.github.config](.github/nuget.github.config) so restores do not depend on the Azure DevOps feed. -Packaging runs produce preview versions in the form `1.0.-preview.`, upload separate artifacts for each RID-specific package, and write a workflow summary with artifact names plus ready-to-use `dotnet tool install` commands. +Packaging runs produce preview versions in the form `1.0.-preview.`, upload separate artifacts for each RID-specific package plus a pointer/base package artifact for the non-RID package ID, and write a workflow summary with artifact names plus ready-to-use `dotnet tool install` commands. ## Command-Line Arguments From c7f3aef8334cf5c067bc7710da1b719a573c8c67 Mon Sep 17 00:00:00 2001 From: Sevo Kukol Date: Thu, 16 Apr 2026 14:12:27 +0200 Subject: [PATCH 23/30] feat: add Component Governance - Component Detection task to pipeline --- .pipelines/CosmosDB-Shell-Official.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.pipelines/CosmosDB-Shell-Official.yml b/.pipelines/CosmosDB-Shell-Official.yml index d901e17..95520c4 100644 --- a/.pipelines/CosmosDB-Shell-Official.yml +++ b/.pipelines/CosmosDB-Shell-Official.yml @@ -155,6 +155,9 @@ extends: projects: $(BuildSolution) custom: "restore" + - task: ComponentGovernanceComponentDetection@0 + displayName: 'Component Governance - Component Detection' + # roslynanalyzers task wraps around dotnet build to enable static analysis - task: RoslynAnalyzers@3 displayName: "DotNetCore build with RoslynAnalyzers" From 0adb738c0e05d4044d3eac14e95665591d4ea831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Thu, 16 Apr 2026 14:58:42 +0200 Subject: [PATCH 24/30] fix: preserve base tool package in artifacts --- .github/workflows/validate-and-package.yml | 7 +-- .pipelines/CosmosDB-Shell-Official.yml | 16 +----- README.md | 58 ++++++---------------- 3 files changed, 17 insertions(+), 64 deletions(-) diff --git a/.github/workflows/validate-and-package.yml b/.github/workflows/validate-and-package.yml index 4be7fc4..5d7cd8d 100644 --- a/.github/workflows/validate-and-package.yml +++ b/.github/workflows/validate-and-package.yml @@ -223,15 +223,10 @@ jobs: ('- `CosmosDBShell-osx-x64-' + $artifactSuffix + '`'), ('- `CosmosDBShell-osx-arm64-' + $artifactSuffix + '`'), '', - 'Download the artifact, extract the `.nupkg` file to a local folder, then install one of these packages:', + 'Download the pointer package artifact and the artifact for your runtime, extract both `.nupkg` files to the same local folder, then install the base package:', '', '```powershell', 'dotnet tool install --global CosmosDBShell --add-source C:\path\to\nupkgs --version ' + $version, - 'dotnet tool install --global CosmosDBShell.win-x64 --add-source C:\path\to\nupkgs --version ' + $version, - 'dotnet tool install --global CosmosDBShell.linux-x64 --add-source C:\path\to\nupkgs --version ' + $version, - 'dotnet tool install --global CosmosDBShell.linux-arm64 --add-source C:\path\to\nupkgs --version ' + $version, - 'dotnet tool install --global CosmosDBShell.osx-x64 --add-source C:\path\to\nupkgs --version ' + $version, - 'dotnet tool install --global CosmosDBShell.osx-arm64 --add-source C:\path\to\nupkgs --version ' + $version, '```' ) $lines -join "`n" | Out-File -FilePath $summary -Encoding utf8 -Append diff --git a/.pipelines/CosmosDB-Shell-Official.yml b/.pipelines/CosmosDB-Shell-Official.yml index 95520c4..b4ea91a 100644 --- a/.pipelines/CosmosDB-Shell-Official.yml +++ b/.pipelines/CosmosDB-Shell-Official.yml @@ -460,17 +460,6 @@ extends: exit 1 } - - task: PowerShell@2 - displayName: "Remove pointer package from shipping outputs" - condition: succeeded() - inputs: - targetType: inline - script: | - $pkgDir = "$(Build.SourcesDirectory)\out\nupkg" - Get-ChildItem -Path (Join-Path $pkgDir "CosmosDBShell.*.nupkg") -ErrorAction SilentlyContinue | Where-Object { - $_.Name -notmatch '^CosmosDBShell\.(win-x64|linux-x64|linux-arm64|osx-x64|osx-arm64)\..+\.nupkg$' - } | Remove-Item -Force - - task: onebranch.pipeline.signing@1 displayName: "Sign NuGet packages" condition: succeeded() @@ -509,9 +498,8 @@ extends: } Write-Host "Non-shipping build outputs cleaned." - # Publish only RID-specific NuGet tool packages. - # dotnet pack may also generate a non-RID pointer package, but feed installs - # should use the platform-specific package IDs directly. + # Keep the pointer package in out\nupkg so it is preserved in pipeline artifacts, + # but publish only the RID-specific NuGet tool packages to the feed. - task: PowerShell@2 displayName: "List NuGet packages before publish" condition: succeeded() diff --git a/README.md b/README.md index 61e2108..4673109 100644 --- a/README.md +++ b/README.md @@ -36,53 +36,29 @@ When consuming build artifacts (`*.nupkg`) from this repo, install as a .NET glo `dotnet tool install` for these packages requires .NET 10 because the tool packages target `net10.0`. -1. Download the NuGet package(s) to a local folder. -2. Install from that folder with `--add-source`. +1. Download the base tool package (`CosmosDBShell..nupkg`) and the package for your runtime to the same local folder. +2. Install from that folder with `--add-source` using the base package ID `CosmosDBShell`. -### Platform package IDs +### Runtime-specific package files -- Linux x64: `CosmosDBShell.linux-x64` -- Linux ARM64: `CosmosDBShell.linux-arm64` -- macOS x64: `CosmosDBShell.osx-x64` -- macOS ARM64: `CosmosDBShell.osx-arm64` -- Windows x64: `CosmosDBShell.win-x64` +- Linux x64: `CosmosDBShell.linux-x64..nupkg` +- Linux ARM64: `CosmosDBShell.linux-arm64..nupkg` +- macOS x64: `CosmosDBShell.osx-x64..nupkg` +- macOS ARM64: `CosmosDBShell.osx-arm64..nupkg` +- Windows x64: `CosmosDBShell.win-x64..nupkg` -### Install commands +### Install command -Linux x64: +After placing the base package and the matching runtime package in the same folder, install with the base package ID: ```bash -dotnet tool install --global CosmosDBShell.linux-x64 --add-source /path/to/nupkgs --version -``` - -Linux ARM64: - -```bash -dotnet tool install --global CosmosDBShell.linux-arm64 --add-source /path/to/nupkgs --version -``` - -macOS x64: - -```bash -dotnet tool install --global CosmosDBShell.osx-x64 --add-source /path/to/nupkgs --version -``` - -macOS ARM64: - -```bash -dotnet tool install --global CosmosDBShell.osx-arm64 --add-source /path/to/nupkgs --version +dotnet tool install --global CosmosDBShell --add-source /path/to/nupkgs --version ``` -Windows x64 (PowerShell): +Windows PowerShell example: ```powershell -dotnet tool install --global CosmosDBShell.win-x64 --add-source C:\path\to\nupkgs --version -``` - -If your feed includes the base tool package (`CosmosDBShell..nupkg`) and its RID package, this also works: - -```bash -dotnet tool install --global CosmosDBShell --add-source /path/to/nupkgs --version +dotnet tool install --global CosmosDBShell --add-source C:\path\to\nupkgs --version ``` ### Use, update, uninstall @@ -107,13 +83,7 @@ List the installed global tools first so you can identify the exact package ID: dotnet tool list --global ``` -Then uninstall the matching package ID. For example, if you installed the Windows x64 RID-specific package: - -```powershell -dotnet tool uninstall --global CosmosDBShell.win-x64 -``` - -If you installed the base package instead: +Then uninstall the tool by its package ID: ```bash dotnet tool uninstall --global CosmosDBShell From d4f87b333424f593b417b45688b5a99a72bd6a14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Thu, 16 Apr 2026 15:50:15 +0200 Subject: [PATCH 25/30] ci: publish base tool package to internal feed --- .pipelines/CosmosDB-Shell-Official.yml | 15 ++++++++++++--- README.md | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.pipelines/CosmosDB-Shell-Official.yml b/.pipelines/CosmosDB-Shell-Official.yml index b4ea91a..57607cb 100644 --- a/.pipelines/CosmosDB-Shell-Official.yml +++ b/.pipelines/CosmosDB-Shell-Official.yml @@ -156,7 +156,7 @@ extends: custom: "restore" - task: ComponentGovernanceComponentDetection@0 - displayName: 'Component Governance - Component Detection' + displayName: "Component Governance - Component Detection" # roslynanalyzers task wraps around dotnet build to enable static analysis - task: RoslynAnalyzers@3 @@ -498,8 +498,8 @@ extends: } Write-Host "Non-shipping build outputs cleaned." - # Keep the pointer package in out\nupkg so it is preserved in pipeline artifacts, - # but publish only the RID-specific NuGet tool packages to the feed. + # Keep the pointer package in out\nupkg so it is preserved in pipeline artifacts + # and available for publishing to the internal feed alongside the RID packages. - task: PowerShell@2 displayName: "List NuGet packages before publish" condition: succeeded() @@ -512,6 +512,15 @@ extends: Write-Host " - $($_.Name) [$($_.Length) bytes]" } - ${{ if and(eq(variables['Build.SourceBranch'], 'refs/heads/main'), eq(parameters.publishNuget, true)) }}: + - task: NuGetCommand@2 + displayName: "Push base NuGet package" + inputs: + command: "push" + packagesToPush: '$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.*.nupkg;!$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.win-x64.*.nupkg;!$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.linux-x64.*.nupkg;!$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.linux-arm64.*.nupkg;!$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.osx-x64.*.nupkg;!$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.osx-arm64.*.nupkg' + nuGetFeedType: "internal" + publishVstsFeed: "CosmosDB/CosmosDBShell" + allowPackageConflicts: true + - task: NuGetCommand@2 displayName: "Push win-x64 NuGet package" inputs: diff --git a/README.md b/README.md index 4673109..60747d4 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ This repo currently uses one GitHub Actions workflow for validation and package GitHub Actions uses [.github/nuget.github.config](.github/nuget.github.config) so restores do not depend on the Azure DevOps feed. -Packaging runs produce preview versions in the form `1.0.-preview.`, upload separate artifacts for each RID-specific package plus a pointer/base package artifact for the non-RID package ID, and write a workflow summary with artifact names plus ready-to-use `dotnet tool install` commands. +Packaging runs produce preview versions in the form `1.0.-preview.`, upload separate artifacts for each RID-specific package plus a pointer/base package artifact for the non-RID package ID, and the Azure pipeline publishes both the base package and the RID-specific packages to the internal feed. ## Command-Line Arguments From 63d19bf9c987604e9622eb1cf6412623d4c7f20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Thu, 16 Apr 2026 15:54:30 +0200 Subject: [PATCH 26/30] Fix unit test error. --- CosmosDBShell.Tests/Parser/CommandStatementTests.cs | 5 ++++- CosmosDBShell.Tests/Shell/ShellTests.cs | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CosmosDBShell.Tests/Parser/CommandStatementTests.cs b/CosmosDBShell.Tests/Parser/CommandStatementTests.cs index cc06eac..e990292 100644 --- a/CosmosDBShell.Tests/Parser/CommandStatementTests.cs +++ b/CosmosDBShell.Tests/Parser/CommandStatementTests.cs @@ -391,7 +391,10 @@ public void ParseCommandStatement_Error() // Should have reported an error for the unexpected } Assert.NotEmpty(errors); - Assert.Contains(errors, e => e.Message.Contains("}") || e.Message.Contains("unexpected")); + var errorSummary = string.Join(", ", errors.Select(e => $"'{e.Message}' at {e.Start} len {e.Length}")); + Assert.True( + errors.Any(e => e.Message.Contains("}", StringComparison.Ordinal) || e.Message.Contains("unexpected", StringComparison.OrdinalIgnoreCase)), + $"Expected a parse error mentioning '}}' or 'unexpected'. Actual errors: [{errorSummary}]"); } [Fact] diff --git a/CosmosDBShell.Tests/Shell/ShellTests.cs b/CosmosDBShell.Tests/Shell/ShellTests.cs index 5dbf356..c90b64f 100644 --- a/CosmosDBShell.Tests/Shell/ShellTests.cs +++ b/CosmosDBShell.Tests/Shell/ShellTests.cs @@ -69,7 +69,9 @@ public async Task VersionCommand_UsesInformationalVersion() .InformationalVersion; if (!string.IsNullOrWhiteSpace(informationalVersion)) { - Assert.Contains("+", actualVersion, StringComparison.Ordinal); + Assert.True( + actualVersion.Contains('+'), + $"Expected version output to include build metadata from AssemblyInformationalVersion. Actual version: '{actualVersion}'. Raw informational version: '{informationalVersion}'. Display version: '{expectedVersion}'."); } } From 67f25abf70ffdd581fe1b0d1ae4cdc1fe9af1254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Thu, 16 Apr 2026 16:00:51 +0200 Subject: [PATCH 27/30] test: align version assertion with display version --- CosmosDBShell.Tests/Shell/ShellTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CosmosDBShell.Tests/Shell/ShellTests.cs b/CosmosDBShell.Tests/Shell/ShellTests.cs index c90b64f..5f21f0c 100644 --- a/CosmosDBShell.Tests/Shell/ShellTests.cs +++ b/CosmosDBShell.Tests/Shell/ShellTests.cs @@ -70,8 +70,8 @@ public async Task VersionCommand_UsesInformationalVersion() if (!string.IsNullOrWhiteSpace(informationalVersion)) { Assert.True( - actualVersion.Contains('+'), - $"Expected version output to include build metadata from AssemblyInformationalVersion. Actual version: '{actualVersion}'. Raw informational version: '{informationalVersion}'. Display version: '{expectedVersion}'."); + !actualVersion.Contains('+'), + $"Expected version output to omit build metadata and match the display version contract. Actual version: '{actualVersion}'. Raw informational version: '{informationalVersion}'. Display version: '{expectedVersion}'."); } } From de0fff8217085fe7514ced58452e4a5e9f1756a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Thu, 16 Apr 2026 16:19:29 +0200 Subject: [PATCH 28/30] ci: harden tool package publishing --- .github/workflows/validate-and-package.yml | 11 ++++++++++- .pipelines/CosmosDB-Shell-Official.yml | 22 +++++++++++----------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.github/workflows/validate-and-package.yml b/.github/workflows/validate-and-package.yml index 5d7cd8d..ddb6f47 100644 --- a/.github/workflows/validate-and-package.yml +++ b/.github/workflows/validate-and-package.yml @@ -122,10 +122,19 @@ jobs: run: | $ErrorActionPreference = 'Stop' $rids = @('win-x64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64') + foreach ($rid in $rids) { + dotnet restore CosmosDBShell/CosmosDBShell.csproj ` + --configfile .github/nuget.github.config ` + -r $rid + + if ($LASTEXITCODE -ne 0) { + throw "RID restore failed for $rid with exit code $LASTEXITCODE." + } + } + foreach ($rid in $rids) { dotnet publish CosmosDBShell/CosmosDBShell.csproj ` --configuration $env:BUILD_CONFIGURATION ` - --configfile .github/nuget.github.config ` --no-restore ` -r $rid ` --output "out/$rid" ` diff --git a/.pipelines/CosmosDB-Shell-Official.yml b/.pipelines/CosmosDB-Shell-Official.yml index 57607cb..c3fa6f3 100644 --- a/.pipelines/CosmosDB-Shell-Official.yml +++ b/.pipelines/CosmosDB-Shell-Official.yml @@ -447,9 +447,9 @@ extends: $_.Name -notmatch '^CosmosDBShell\.(win-x64|linux-x64|linux-arm64|osx-x64|osx-arm64)\..+\.nupkg$' } - if ($pointerPackages -and $pointerPackages.Count -gt 1) { + if (-not $pointerPackages -or $pointerPackages.Count -ne 1) { $names = @($pointerPackages | ForEach-Object { $_.Name }) - Write-Error "Expected at most one pointer package (non-RID). Found: $($names -join ', ')" + Write-Error "Expected exactly one pointer package (non-RID). Found: $($names -join ', ')" exit 1 } @@ -512,15 +512,6 @@ extends: Write-Host " - $($_.Name) [$($_.Length) bytes]" } - ${{ if and(eq(variables['Build.SourceBranch'], 'refs/heads/main'), eq(parameters.publishNuget, true)) }}: - - task: NuGetCommand@2 - displayName: "Push base NuGet package" - inputs: - command: "push" - packagesToPush: '$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.*.nupkg;!$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.win-x64.*.nupkg;!$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.linux-x64.*.nupkg;!$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.linux-arm64.*.nupkg;!$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.osx-x64.*.nupkg;!$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.osx-arm64.*.nupkg' - nuGetFeedType: "internal" - publishVstsFeed: "CosmosDB/CosmosDBShell" - allowPackageConflicts: true - - task: NuGetCommand@2 displayName: "Push win-x64 NuGet package" inputs: @@ -566,6 +557,15 @@ extends: publishVstsFeed: "CosmosDB/CosmosDBShell" allowPackageConflicts: true + - task: NuGetCommand@2 + displayName: "Push base NuGet package" + inputs: + command: "push" + packagesToPush: '$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.*.nupkg;!$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.win-x64.*.nupkg;!$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.linux-x64.*.nupkg;!$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.linux-arm64.*.nupkg;!$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.osx-x64.*.nupkg;!$(Build.SourcesDirectory)\out\nupkg\CosmosDBShell.osx-arm64.*.nupkg' + nuGetFeedType: "internal" + publishVstsFeed: "CosmosDB/CosmosDBShell" + allowPackageConflicts: true + - job: CodeQLAnalyze displayName: CodeQL (C#) pool: From 18a4bb12ccaa90547d46152dad5c494487f7c1c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Thu, 16 Apr 2026 16:43:44 +0200 Subject: [PATCH 29/30] Fix workflow. --- .github/workflows/validate-and-package.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/validate-and-package.yml b/.github/workflows/validate-and-package.yml index ddb6f47..70448d6 100644 --- a/.github/workflows/validate-and-package.yml +++ b/.github/workflows/validate-and-package.yml @@ -130,9 +130,7 @@ jobs: if ($LASTEXITCODE -ne 0) { throw "RID restore failed for $rid with exit code $LASTEXITCODE." } - } - foreach ($rid in $rids) { dotnet publish CosmosDBShell/CosmosDBShell.csproj ` --configuration $env:BUILD_CONFIGURATION ` --no-restore ` From 85dc4617b4ff78a3aa1327531ae9329e2b323f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Thu, 16 Apr 2026 16:57:17 +0200 Subject: [PATCH 30/30] ci: fix pack step to restore all RIDs before packaging The pack step was failing with NETSDK1047 because it ran with --no-build --no-restore, but the project.assets.json only had the last RID from the publish loop. Tool packaging with ToolPackageRuntimeIdentifiers needs assets for ALL RIDs. Fix: - Add restore without -r flag before pack (restores all RIDs) - Remove --no-build so pack can build each RID package --- .github/workflows/validate-and-package.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/validate-and-package.yml b/.github/workflows/validate-and-package.yml index 70448d6..2214ddc 100644 --- a/.github/workflows/validate-and-package.yml +++ b/.github/workflows/validate-and-package.yml @@ -150,9 +150,13 @@ jobs: shell: pwsh run: | New-Item -ItemType Directory -Path out/nupkg -Force | Out-Null + + # Restore for all RIDs so pack can build packages for each + dotnet restore CosmosDBShell/CosmosDBShell.csproj ` + --configfile .github/nuget.github.config + dotnet pack CosmosDBShell/CosmosDBShell.csproj ` --configuration $env:BUILD_CONFIGURATION ` - --no-build ` --no-restore ` --output out/nupkg ` /p:PackageVersion=${{ steps.version.outputs.package_version }} `