diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml index 836c94d22..724b3dea5 100644 --- a/.azure-pipelines/ci-build.yml +++ b/.azure-pipelines/ci-build.yml @@ -66,6 +66,11 @@ extends: inputs: version: 8.x + - task: UseDotNet@2 + displayName: 'Use .NET 10 for hidi tests' + inputs: + version: 10.x + # Install the nuget tool. - task: NuGetToolInstaller@1 displayName: 'Use NuGet >=6.11.0' @@ -73,6 +78,21 @@ extends: versionSpec: '>=6.11.0' checkLatest: true + - task: NuGetAuthenticate@1 + displayName: 'Authenticate approved NuGet feed' + + - pwsh: | + @' + + + + + + + + '@ | Set-Content -Path '$(Build.SourcesDirectory)\nuget.config' + displayName: 'Configure approved NuGet feed' + # Build the Product project - task: DotNetCoreCLI@2 displayName: 'build' @@ -80,19 +100,26 @@ extends: projects: '$(Build.SourcesDirectory)\Microsoft.OpenApi.OData.sln' arguments: '--configuration $(BuildConfiguration) --no-incremental' - # Run the Unit test + # Both test projects are in the solution, but SDK10 cannot run MTP via VSTest. + # Keep OData on VSTest and invoke the hidi MTP executable separately. - task: DotNetCoreCLI@2 displayName: 'test' inputs: command: test - projects: '$(Build.SourcesDirectory)\Microsoft.OpenApi.OData.sln' + projects: '$(Build.SourcesDirectory)\test\Microsoft.OpenAPI.OData.Reader.Tests\Microsoft.OpenAPI.OData.Reader.Tests.csproj' arguments: '--configuration $(BuildConfiguration) --no-build' + - pwsh: | + dotnet run --project test\Microsoft.OpenApi.Hidi.Tests\Microsoft.OpenApi.Hidi.Tests.csproj -c $(buildConfiguration) --no-build -- --minimum-expected-tests 1 + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + displayName: 'Test hidi (Microsoft.Testing.Platform)' + - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@5 displayName: 'ESRP CodeSigning' inputs: ConnectedServiceName: 'Federated DevX ESRP Managed Identity Connection' - FolderPath: 'src' + FolderPath: 'bin\$(BuildConfiguration)' + Pattern: 'Microsoft.OpenApi.OData.Reader.dll' AppRegistrationClientId: '65035b7f-7357-4f29-bf25-c5ee5c3949f8' AppRegistrationTenantId: 'cdc5aeea-15c5-4db6-b079-fcadd2505dc2' AuthAKVName: 'akv-prod-eastus' @@ -228,7 +255,9 @@ extends: vmImage: ubuntu-latest steps: - pwsh: | - $artifactName = Get-ChildItem -Path $(Pipeline.Workspace) -Filter Microsoft.OpenApi.*.nupkg -recurse | select -First 1 + $artifacts = @(Get-ChildItem -Path $(Pipeline.Workspace) -Filter Microsoft.OpenApi.OData.*.nupkg -Recurse) + if ($artifacts.Count -ne 1) { throw "Expected exactly one OData package; found $($artifacts.Count)." } + $artifactName = $artifacts[0] $artifactVersion= $artifactName.Name -replace "Microsoft.OpenApi.OData.", "" -replace ".nupkg", "" #Set Variable $artifactName and $artifactVersion Write-Host "##vso[task.setvariable variable=artifactVersion; isSecret=false;]$artifactVersion" @@ -244,5 +273,5 @@ extends: tag: 'v$(artifactVersion)' title: 'v$(artifactVersion)' releaseNotesSource: inline - assets: '$(Pipeline.Workspace)\**\*.nupkg' + assets: '$(Pipeline.Workspace)\Microsoft.OpenApi.OData.*.nupkg' addChangeLog: false diff --git a/.azure-pipelines/hidi-release.yml b/.azure-pipelines/hidi-release.yml new file mode 100644 index 000000000..b746bfe3f --- /dev/null +++ b/.azure-pipelines/hidi-release.yml @@ -0,0 +1,252 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +name: Hidi_$(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r) + +trigger: + branches: + include: + - support/v2 + tags: + include: + - hidi-v2.* +pr: + branches: + include: + - support/v2 + +variables: + buildConfiguration: Release + # Keep OFF until the destination PR lands and source publishing is cut over. + hidiPublishingEnabled: 'false' + +resources: + repositories: + - repository: 1ESPipelineTemplates + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release + +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + parameters: + pool: + name: Azure-Pipelines-1ESPT-ExDShared + image: windows-latest + os: windows + stages: + - stage: build + jobs: + - job: hidi + templateContext: + outputs: + - output: pipelineArtifact + artifactName: Hidi + targetPath: '$(Build.ArtifactStagingDirectory)\hidi' + steps: + - task: UseDotNet@2 + displayName: Use .NET 6 for ESRP + inputs: + version: 6.x + - task: UseDotNet@2 + displayName: Use .NET 8 + inputs: + version: 8.x + - task: UseDotNet@2 + displayName: Use .NET 10 + inputs: + version: 10.x + - task: NuGetToolInstaller@1 + inputs: + versionSpec: '>=6.11.0' + checkLatest: true + - task: NuGetAuthenticate@1 + displayName: Authenticate approved NuGet feed + - pwsh: | + @' + + + + + + + + '@ | Set-Content -Path '$(Build.SourcesDirectory)\nuget.config' + displayName: Configure approved NuGet feed + - pwsh: | + $version = ([xml](Get-Content src\Microsoft.OpenApi.Hidi\Microsoft.OpenApi.Hidi.csproj)).Project.PropertyGroup.Version + if ($version -notmatch '^2\.\d+\.\d+(-[0-9A-Za-z.-]+)?$') { throw "Expected a hidi 2.x version, got $version." } + if ('$(Build.SourceBranch)' -like 'refs/tags/*' -and '$(Build.SourceBranch)' -ne "refs/tags/hidi-v$version") { + throw "Hidi tag must exactly match the hidi project version." + } + Write-Host "##vso[task.setvariable variable=HidiVersion]$version" + dotnet build src\Microsoft.OpenApi.Hidi\Microsoft.OpenApi.Hidi.csproj -c $(buildConfiguration) -p:GeneratePackageOnBuild=false + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + dotnet run --project test\Microsoft.OpenApi.Hidi.Tests\Microsoft.OpenApi.Hidi.Tests.csproj -c $(buildConfiguration) -- --minimum-expected-tests 1 + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + dotnet publish src\Microsoft.OpenApi.Hidi\Microsoft.OpenApi.Hidi.csproj -c $(buildConfiguration) -r win-x64 --self-contained true -p:PublishSingleFile=true -p:PackAsTool=false -p:GeneratePackageOnBuild=false -o artifacts\hidi\win-x64 + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + & .\artifacts\hidi\win-x64\Microsoft.OpenApi.Hidi.exe --help + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + New-Item -ItemType Directory -Force artifacts\hidi\signing | Out-Null + Copy-Item src\Microsoft.OpenApi.Hidi\bin\$(buildConfiguration)\net8.0\Microsoft.OpenApi.Hidi.dll artifacts\hidi\signing + Copy-Item artifacts\hidi\win-x64\Microsoft.OpenApi.Hidi.exe artifacts\hidi\signing + displayName: Build, test and stage hidi binaries + - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@5 + displayName: Sign hidi DLL and Windows executable + inputs: + ConnectedServiceName: Federated DevX ESRP Managed Identity Connection + FolderPath: 'artifacts\hidi\signing' + Pattern: 'Microsoft.OpenApi.Hidi.*' + AppRegistrationClientId: '65035b7f-7357-4f29-bf25-c5ee5c3949f8' + AppRegistrationTenantId: 'cdc5aeea-15c5-4db6-b079-fcadd2505dc2' + AuthAKVName: akv-prod-eastus + AuthCertName: ReferenceLibraryPrivateCert + AuthSignCertName: ReferencePackagePublisherCertificate + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-230012", + "operationSetCode": "SigntoolSign", + "parameters": [ + { "parameterName": "OpusName", "parameterValue": "Microsoft" }, + { "parameterName": "OpusInfo", "parameterValue": "http://www.microsoft.com" }, + { "parameterName": "FileDigest", "parameterValue": "/fd \"SHA256\"" }, + { "parameterName": "PageHash", "parameterValue": "/NPH" }, + { "parameterName": "TimeStamp", "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" } + ], + "toolName": "sign", + "toolVersion": "1.0" + }, + { + "keyCode": "CP-230012", + "operationSetCode": "SigntoolVerify", + "parameters": [], + "toolName": "sign", + "toolVersion": "1.0" + } + ] + SessionTimeout: '20' + MaxConcurrency: '50' + MaxRetryAttempts: '5' + PendingAnalysisWaitTimeoutMinutes: '5' + - pwsh: | + Copy-Item artifacts\hidi\signing\Microsoft.OpenApi.Hidi.dll src\Microsoft.OpenApi.Hidi\bin\$(buildConfiguration)\net8.0\Microsoft.OpenApi.Hidi.dll + Copy-Item artifacts\hidi\signing\Microsoft.OpenApi.Hidi.exe artifacts\hidi\win-x64\Microsoft.OpenApi.Hidi.exe + New-Item -ItemType Directory -Force '$(Build.ArtifactStagingDirectory)\hidi' | Out-Null + dotnet pack src\Microsoft.OpenApi.Hidi\Microsoft.OpenApi.Hidi.csproj -c $(buildConfiguration) --no-build -o '$(Build.ArtifactStagingDirectory)\hidi' + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + Compress-Archive -Path artifacts\hidi\win-x64\* -DestinationPath '$(Build.ArtifactStagingDirectory)\hidi\hidi-win-x64-$(HidiVersion).zip' + Copy-Item artifacts\hidi\win-x64\Microsoft.OpenApi.Hidi.exe '$(Build.ArtifactStagingDirectory)\hidi\Microsoft.OpenApi.Hidi.exe' + displayName: Pack signed hidi binaries + - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@5 + displayName: Sign hidi NuGet package + inputs: + ConnectedServiceName: Federated DevX ESRP Managed Identity Connection + FolderPath: '$(Build.ArtifactStagingDirectory)\hidi' + Pattern: 'Microsoft.OpenApi.Hidi.$(HidiVersion).nupkg' + AppRegistrationClientId: '65035b7f-7357-4f29-bf25-c5ee5c3949f8' + AppRegistrationTenantId: 'cdc5aeea-15c5-4db6-b079-fcadd2505dc2' + AuthAKVName: akv-prod-eastus + AuthCertName: ReferenceLibraryPrivateCert + AuthSignCertName: ReferencePackagePublisherCertificate + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-401405", + "operationSetCode": "NuGetSign", + "parameters": [], + "toolName": "sign", + "toolVersion": "1.0" + }, + { + "keyCode": "CP-401405", + "operationSetCode": "NuGetVerify", + "parameters": [], + "toolName": "sign", + "toolVersion": "1.0" + } + ] + SessionTimeout: '60' + MaxConcurrency: '50' + MaxRetryAttempts: '5' + PendingAnalysisWaitTimeoutMinutes: '5' + - stage: publish + dependsOn: build + condition: and(succeeded(), eq(variables.hidiPublishingEnabled, 'true'), startsWith(variables['Build.SourceBranch'], 'refs/tags/hidi-v2.')) + jobs: + - deployment: publish_hidi + templateContext: + type: releaseJob + isProduction: true + inputs: + - input: pipelineArtifact + artifactName: Hidi + targetPath: '$(Pipeline.Workspace)\hidi' + environment: nuget-org + strategy: + runOnce: + deploy: + steps: + - pwsh: | + $tag = '$(Build.SourceBranch)' -replace '^refs/tags/hidi-v', '' + if ([version]($tag -split '-')[0] -le [version]'2.12.2') { throw "Never republish the source migration baseline or an older hidi version." } + $package = "$(Pipeline.Workspace)\hidi\Microsoft.OpenApi.Hidi.$tag.nupkg" + if (-not (Test-Path $package)) { throw "Missing exact hidi package: $package" } + Write-Host "##vso[task.setvariable variable=HidiReleaseVersion]$tag" + displayName: Verify new hidi release version and exact package + - task: 1ES.PublishNuget@1 + displayName: Publish hidi NuGet tool + inputs: + packagesToPush: '$(Pipeline.Workspace)\hidi\Microsoft.OpenApi.Hidi.$(HidiReleaseVersion).nupkg' + nuGetFeedType: external + publishFeedCredentials: OpenAPI Nuget Connection + packageParentPath: '$(Pipeline.Workspace)\hidi' + - task: GitHubRelease@1 + displayName: Attach signed hidi release artifacts + inputs: + gitHubConnection: Github-MaggieKimani1 + action: edit + tagSource: userSpecifiedTag + tag: 'hidi-v$(HidiReleaseVersion)' + title: 'Hidi v$(HidiReleaseVersion)' + assets: | + $(Pipeline.Workspace)\hidi\Microsoft.OpenApi.Hidi.$(HidiReleaseVersion).nupkg + $(Pipeline.Workspace)\hidi\Microsoft.OpenApi.Hidi.exe + $(Pipeline.Workspace)\hidi\hidi-win-x64-$(HidiReleaseVersion).zip + addChangeLog: false + - deployment: publish_container + templateContext: + type: releaseJob + isProduction: true + environment: hidi-container + strategy: + runOnce: + deploy: + pool: + vmImage: ubuntu-latest + steps: + - checkout: self + - pwsh: | + $version = ([xml](Get-Content src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj)).Project.PropertyGroup.Version + if ('$(Build.SourceBranch)' -ne "refs/tags/hidi-v$version") { throw "Hidi tag does not match project version." } + if ([version]($version -split '-')[0] -le [version]'2.12.2') { throw "Do not republish the migration baseline or an older hidi version." } + Write-Host "##vso[task.setvariable variable=HidiReleaseVersion]$version" + displayName: Read independent hidi version + - task: AzureCLI@2 + displayName: Publish multi-platform hidi image + inputs: + azureSubscription: ACR Images Push Service Connection + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + set -euo pipefail + az acr login --name msgraphprodregistry + docker run --privileged --rm tonistiigi/binfmt --install all + docker buildx create --use + docker buildx build --platform linux/amd64,linux/arm64/v8 \ + --tag "msgraphprodregistry.azurecr.io/public/openapi/hidi:$(HidiReleaseVersion)" \ + --tag "msgraphprodregistry.azurecr.io/public/openapi/hidi:latest" \ + --push . diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..b9099d2c0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +**/bin +**/obj +**/artifacts +.git +.vs +.idea +test +**/*.snk +!tool/Microsoft.OpenApi.Hidi.public.snk +!tool/Microsoft.OpenApi.OData.public.snk diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 660e7b22b..afa7c5c2b 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -19,7 +19,9 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v5 with: - dotnet-version: 8.0.x + dotnet-version: | + 8.0.x + 10.0.x - name: Data gatherer id: data_gatherer @@ -93,6 +95,55 @@ jobs: -c Release } + - name: Run hidi tests (Microsoft.Testing.Platform) + shell: pwsh + run: | + dotnet run --project .\test\Microsoft.OpenApi.Hidi.Tests\Microsoft.OpenApi.Hidi.Tests.csproj -c Release -- --minimum-expected-tests 1 + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + - name: Pack and install hidi locally + shell: pwsh + run: | + dotnet pack .\src\Microsoft.OpenApi.Hidi\Microsoft.OpenApi.Hidi.csproj -c Release --no-build -o .\artifacts\hidi\nuget + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + .\install-tool.ps1 + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + $workspace = (Get-Location).Path + $fixtures = Join-Path $workspace 'test' 'Microsoft.OpenApi.Hidi.Tests' 'UtilityFiles' + $outputs = Join-Path $workspace 'artifacts' 'hidi' + $hidi = Join-Path $outputs 'installed' 'hidi' + $sampleInput = Join-Path $fixtures 'SampleOpenApi.yml' + $csdlInput = Join-Path $fixtures 'Todo.xml' + $jsonOutput = Join-Path $outputs 'openapi.json' + $csdlOutput = Join-Path $outputs 'csdl.yaml' + $diagramOutput = Join-Path $outputs 'diagram.md' + & $hidi --help + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + & $hidi validate --openapi $sampleInput + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + & $hidi transform --openapi $sampleInput --format json --version 3.0 --output $jsonOutput --clean-output + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + & $hidi transform --csdl $csdlInput --format yaml --version 3.0 --output $csdlOutput --clean-output + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + $document = Get-Content $jsonOutput -Raw | ConvertFrom-Json + if ($document.openapi -notlike '3.0.*' -or @($document.paths.PSObject.Properties).Count -eq 0) { + throw "Expected an OpenAPI 3.0 document with paths from SampleOpenApi." + } + $csdlDocument = Get-Content $csdlOutput -Raw + if ($csdlDocument -notmatch '(?m)^openapi:\s*3\.0\.' -or $csdlDocument -notmatch '(?m)^ /Todos:\s*$' -or $csdlDocument -notmatch '(?m)^ schemas:\s*$') { + throw "Expected OpenAPI 3.0, /Todos paths and schemas in the CSDL YAML output." + } + & $hidi show --openapi $sampleInput --output $diagramOutput + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + if (-not (Select-String $diagramOutput -Pattern 'mermaid' -Quiet)) { throw "Expected a Mermaid diagram from hidi show." } + + - name: Upload hidi NuGet artifact (not a release) + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: hidi-nuget + path: artifacts/hidi/nuget + if-no-files-found: error + # - if: steps.tag_generator.outputs.new_version != '' # name: Upload NuGet packages as artifacts # id: ul_packages_artifact @@ -101,6 +152,46 @@ jobs: # name: NuGet packages # path: Artifacts/NuGet/ + hidi-windows: + name: Hidi Windows executable + runs-on: windows-latest + permissions: + contents: read + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5 + with: + dotnet-version: | + 8.0.x + 10.0.x + - name: Publish and smoke-test Windows executable + shell: pwsh + run: | + dotnet publish .\src\Microsoft.OpenApi.Hidi\Microsoft.OpenApi.Hidi.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:PackAsTool=false -p:GeneratePackageOnBuild=false -o .\artifacts\hidi\win-x64 + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + & .\artifacts\hidi\win-x64\Microsoft.OpenApi.Hidi.exe --help + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + Compress-Archive -Path .\artifacts\hidi\win-x64\* -DestinationPath .\artifacts\hidi\hidi-win-x64.zip + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: hidi-windows-executable + path: | + artifacts/hidi/hidi-win-x64.zip + artifacts/hidi/win-x64/Microsoft.OpenApi.Hidi.exe + if-no-files-found: error + + hidi-docker: + name: Hidi Docker build + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - name: Build and smoke-test Docker image (no push) + run: | + docker build --tag hidi:ci . + docker run --rm hidi:ci --help + cd: if: needs.ci.outputs.is_default_branch == 'true' && needs.ci.outputs.latest_version != '' name: Continuous Deployment diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index aaa85d12f..fdc79fd03 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -44,7 +44,9 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v5 with: - dotnet-version: 8.0.x + dotnet-version: | + 8.0.x + 10.0.x # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL @@ -62,7 +64,7 @@ jobs: # uses: github/codeql-action/autobuild@v2 - name: build - run: dotnet build src\Microsoft.OpenApi.OData.Reader\Microsoft.OpenAPI.OData.Reader.csproj -c Release + run: dotnet build Microsoft.OpenApi.OData.sln -c Release # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl diff --git a/.github/workflows/release-please-gha.yml b/.github/workflows/release-please-gha.yml index e54172dac..5f46bf25e 100644 --- a/.github/workflows/release-please-gha.yml +++ b/.github/workflows/release-please-gha.yml @@ -25,19 +25,41 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: app-id: ${{ vars.RELEASE_PLEASE_TOKEN_PROVIDER_APP_ID }} private-key: ${{ secrets.RELEASE_PLEASE_TOKEN_PROVIDER_PEM }} - name: Release Please - uses: googleapis/release-please-action@v4 + uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4 with: token: ${{ steps.app-token.outputs.token }} config-file: release-please-config.json manifest-file: .release-please-manifest.json target-branch: ${{ github.ref_name }} + + release-please-hidi: + # Enable only after the destination PR lands and source publishing is cut over. + if: ${{ false }} + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + app-id: ${{ vars.RELEASE_PLEASE_TOKEN_PROVIDER_APP_ID }} + private-key: ${{ secrets.RELEASE_PLEASE_TOKEN_PROVIDER_PEM }} + - name: Release Please (independent hidi version) + uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4 + with: + token: ${{ steps.app-token.outputs.token }} + config-file: hidi-release-please-config.json + manifest-file: .hidi-release-please-manifest.json + target-branch: ${{ github.ref_name }} diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index d18fbb410..3b5522a61 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -43,7 +43,9 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v5 with: - dotnet-version: 8.0.x + dotnet-version: | + 8.0.x + 10.0.x - uses: actions/checkout@v6 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis @@ -62,8 +64,12 @@ jobs: CoverletOutputFormat: 'opencover' # https://github.com/microsoft/vstest/issues/4014#issuecomment-1307913682 shell: pwsh run: | - dotnet tool run dotnet-sonarscanner begin /k:"microsoft_OpenAPI.NET.OData" /o:"microsoft" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="test/**/coverage.net8.0.opencover.xml" + dotnet tool run dotnet-sonarscanner begin /k:"microsoft_OpenAPI.NET.OData" /o:"microsoft" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="test/**/coverage.net8.0.opencover.xml,artifacts/hidi/coverage/*opencover*.xml" dotnet workload restore dotnet build - dotnet test Microsoft.OpenApi.OData.sln --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover + dotnet test test\Microsoft.OpenAPI.OData.Reader.Tests\Microsoft.OpenAPI.OData.Reader.Tests.csproj --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + dotnet run --project test\Microsoft.OpenApi.Hidi.Tests\Microsoft.OpenApi.Hidi.Tests.csproj --no-build -- --minimum-expected-tests 1 --coverlet --coverlet-output-format opencover --coverlet-include "[Microsoft.OpenApi.Hidi]*" --results-directory artifacts\hidi\coverage + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + if (-not (Get-ChildItem artifacts\hidi\coverage -Filter '*opencover*.xml')) { throw "Hidi OpenCover report was not generated." } dotnet tool run dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" diff --git a/.hidi-release-please-manifest.json b/.hidi-release-please-manifest.json new file mode 100644 index 000000000..ddfb5d579 --- /dev/null +++ b/.hidi-release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "2.12.2" +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 334509bee..d44c94f23 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,30 @@ OpenAPI.net.OData is a mono-repo containing source code for the following packag |----------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [Microsoft.OpenAPI.OData](./README.md) | [![NuGet Version](https://img.shields.io/nuget/vpre/Microsoft.OpenAPI.OData?label=Latest&logo=nuget)](https://www.nuget.org/packages/Microsoft.OpenAPI.OData/) | +## Hidi + +[Microsoft.OpenApi.Hidi](src/Microsoft.OpenApi.Hidi/readme.md) is a .NET tool, +not an OData library package. Install the .NET 10 SDK and .NET 8 runtime to build +and test both projects. Do not change the solution-wide test runner: OData uses +VSTest and hidi uses Microsoft.Testing.Platform. + +```powershell +dotnet build Microsoft.OpenApi.OData.sln -c Release +dotnet test test\Microsoft.OpenAPI.OData.Reader.Tests\Microsoft.OpenAPI.OData.Reader.Tests.csproj -c Release +dotnet run --project test\Microsoft.OpenApi.Hidi.Tests\Microsoft.OpenApi.Hidi.Tests.csproj -c Release -- --minimum-expected-tests 1 +``` + +Hidi uses published OpenAPI core/YAML packages and the local OData project. Its +public-only signing key preserves assembly identity and friend-assembly access; +never add the source repository's private strong-name key. Production signing +is handled only by the official Azure pipeline. + +Hidi's version is in its own project and `.hidi-release-please-manifest.json`. +Its `hidi-v2.*` tags must not trigger OData publishing. Version 2.12.2 is the +already-published migration baseline, not a new release. Hidi release automation +and publishing remain disabled until source cutover. History-import migration +PRs must be merged with a merge commit, never squash or rebase. + OpenAPI.net.OData is open to contributions. There are a couple of different recommended paths to get contributions into the released version of this library. __NOTE__ A signed a contribution license agreement is required for all contributions, and is checked automatically on new pull requests. Please read and sign [the agreement](https://cla.microsoft.com/) before starting any work for this repository. diff --git a/Directory.Build.props b/Directory.Build.props index 6192a4ece..47bf01127 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,7 +5,7 @@ Microsoft MIT true - https://github.com/Microsoft/OpenAPI.NET + https://github.com/microsoft/OpenAPI.NET.OData https://github.com/Microsoft/OpenAPI.NET.OData https://github.com/microsoft/OpenAPI.NET.OData/releases false diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..2ec9d0a9b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build-env +WORKDIR /app + +COPY ./src ./hidi/src +COPY ./Directory.Build.props ./hidi/Directory.Build.props +COPY ./Build.props ./hidi/Build.props +COPY ./build.root ./hidi/build.root +COPY ./global.json ./hidi/global.json +COPY ./tool/Microsoft.OpenApi.OData.public.snk ./hidi/tool/Microsoft.OpenApi.OData.public.snk +COPY ./tool/Microsoft.OpenApi.Hidi.public.snk ./hidi/tool/Microsoft.OpenApi.Hidi.public.snk +COPY ./README.md ./hidi/README.md +WORKDIR /app/hidi +RUN dotnet publish ./src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj -c Release -o /app/publish -p:GeneratePackageOnBuild=false -p:HidiPublicSignBuild=true + +FROM mcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled AS runtime +WORKDIR /app + +COPY --from=build-env /app/publish ./ + +VOLUME /app/output +VOLUME /app/openapi.yml +VOLUME /app/api.csdl +VOLUME /app/collection.json +ENV HIDI_CONTAINER=true DOTNET_TieredPGO=1 DOTNET_TC_QuickJitForLoops=1 +ENTRYPOINT ["dotnet", "Microsoft.OpenApi.Hidi.dll"] +LABEL description="# Welcome to Hidi \ +To start transforming OpenAPI documents checkout [the getting started documentation](https://github.com/microsoft/OpenAPI.NET.OData/tree/support/v2/src/Microsoft.OpenApi.Hidi) \ +[Source dockerfile](https://github.com/microsoft/OpenAPI.NET.OData/blob/support/v2/Dockerfile)" +LABEL org.opencontainers.image.source="https://github.com/microsoft/OpenAPI.NET.OData" diff --git a/Microsoft.OpenApi.OData.sln b/Microsoft.OpenApi.OData.sln index 085f0a157..5cb529f5d 100644 --- a/Microsoft.OpenApi.OData.sln +++ b/Microsoft.OpenApi.OData.sln @@ -16,12 +16,24 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenApi.Hidi", "src\Microsoft.OpenApi.Hidi\Microsoft.OpenApi.Hidi.csproj", "{394FE6DA-3913-4421-84D2-0AF945E974FB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenApi.Hidi.Tests", "test\Microsoft.OpenApi.Hidi.Tests\Microsoft.OpenApi.Hidi.Tests.csproj", "{FD5933AF-2461-4B9F-A98B-234F1593924D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {394FE6DA-3913-4421-84D2-0AF945E974FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {394FE6DA-3913-4421-84D2-0AF945E974FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {394FE6DA-3913-4421-84D2-0AF945E974FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {394FE6DA-3913-4421-84D2-0AF945E974FB}.Release|Any CPU.Build.0 = Release|Any CPU + {FD5933AF-2461-4B9F-A98B-234F1593924D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FD5933AF-2461-4B9F-A98B-234F1593924D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD5933AF-2461-4B9F-A98B-234F1593924D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FD5933AF-2461-4B9F-A98B-234F1593924D}.Release|Any CPU.Build.0 = Release|Any CPU {FF3ACD93-19E0-486C-9C0F-FA1C2E7FC8C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FF3ACD93-19E0-486C-9C0F-FA1C2E7FC8C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {FF3ACD93-19E0-486C-9C0F-FA1C2E7FC8C2}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/README.md b/README.md index b02123072..49bca1a89 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,23 @@ The `GetEdmModel()` method can load a model in 3 ways: The OpenAPI.OData.reader nuget package is at: [https://www.nuget.org/packages/Microsoft.OpenApi.OData/](https://www.nuget.org/packages/Microsoft.OpenApi.OData) +## Hidi command-line tool + +[Hidi](src/Microsoft.OpenApi.Hidi/readme.md) validates, transforms, filters, and +visualizes OpenAPI documents and converts OData CSDL to OpenAPI. It is distributed +as the `Microsoft.OpenApi.Hidi` NuGet tool, a Windows executable, and a Docker image. +The command remains `hidi`. + +The `support/v2` branch maintains hidi 2.x separately from the OData library +version. Hidi requires .NET 8 to run; building the repository and running hidi +tests requires the .NET 10 SDK. Existing OData tests still use .NET 8 and VSTest. + +Hidi's filtered history was imported with a merge, not squashed. **Merge this +migration PR using a merge commit, not squash or rebase**, so the canonical +imported commits can also be retained by the later main-branch migration. +Destination hidi release and production publishing are disabled until source +cutover; OpenAPI.NET remains the publisher in the meantime. + --- # Contributing diff --git a/global.json b/global.json new file mode 100644 index 000000000..d46d21e50 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "10.0.100", + "rollForward": "latestFeature", + "allowPrerelease": false + } +} diff --git a/hidi-release-please-config.json b/hidi-release-please-config.json new file mode 100644 index 000000000..6e95505cb --- /dev/null +++ b/hidi-release-please-config.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "bootstrap-sha": "8ebc6abbb1da683db4b5bc0928183dec947f2f02", + "release-type": "simple", + "include-component-in-tag": true, + "include-v-in-tag": true, + "exclude-paths": [ + ".azure-pipelines", + ".github", + ".idea", + ".vs", + ".vscode", + "src/Microsoft.OpenApi.OData.Reader", + "src/OoasGui", + "src/OoasUtil", + "test/Microsoft.OpenAPI.OData.Reader.Tests", + "docs", + "CHANGELOG.md", + "Directory.Build.props", + "release-please-config.json", + ".release-please-manifest.json" + ], + "packages": { + ".": { + "package-name": "Microsoft.OpenApi.Hidi", + "component": "hidi", + "changelog-path": "src/Microsoft.OpenApi.Hidi/CHANGELOG.md", + "extra-files": [ + { + "type": "xml", + "path": "src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj", + "xpath": "//Project/PropertyGroup/Version" + } + ] + } + } +} diff --git a/install-tool.ps1 b/install-tool.ps1 new file mode 100644 index 000000000..4d1a74c07 --- /dev/null +++ b/install-tool.ps1 @@ -0,0 +1,25 @@ +$ErrorActionPreference = 'Stop' + +$projectPath = Join-Path $PSScriptRoot 'src\Microsoft.OpenApi.Hidi\Microsoft.OpenApi.Hidi.csproj' +$version = ([xml](Get-Content $projectPath)).Project.PropertyGroup.Version +$packageDirectory = Join-Path $PSScriptRoot 'artifacts\hidi\nuget' +$packagePath = Join-Path $packageDirectory "Microsoft.OpenApi.Hidi.$version.nupkg" +$toolDirectory = Join-Path $PSScriptRoot 'artifacts\hidi\installed' +$configPath = Join-Path $PSScriptRoot 'tool\hidi-local-nuget.config' + +if (-not (Test-Path $packagePath)) { + throw "Missing $packagePath. Pack the hidi project in Release configuration before installing." +} +if ((Test-Path (Join-Path $toolDirectory 'hidi.exe')) -or (Test-Path (Join-Path $toolDirectory 'hidi'))) { + dotnet tool uninstall --tool-path $toolDirectory Microsoft.OpenApi.Hidi + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +} +dotnet tool install --tool-path $toolDirectory --configfile $configPath --add-source $packageDirectory --no-cache --version $version Microsoft.OpenApi.Hidi +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +$installedDlls = @(Get-ChildItem $toolDirectory -Force -Recurse -Filter Microsoft.OpenApi.Hidi.dll) +if ($installedDlls.Count -ne 1) { throw 'Expected exactly one installed hidi DLL.' } +$builtDll = Join-Path $PSScriptRoot 'src\Microsoft.OpenApi.Hidi\bin\Release\net8.0\Microsoft.OpenApi.Hidi.dll' +if ((Get-FileHash $installedDlls[0].FullName).Hash -ne (Get-FileHash $builtDll).Hash) { + throw 'Installed hidi does not match the locally built package.' +} \ No newline at end of file diff --git a/release-please-config.json b/release-please-config.json index 0e431d4d9..75e417d68 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -5,7 +5,21 @@ ".github", ".idea", ".vs", - ".vscode" + ".vscode", + "src/Microsoft.OpenApi.Hidi", + "src/Microsoft.OpenApi.Tool", + "src/Microsoft.Hidi", + "Microsoft.OpenApi.Hidi.Tests", + "test/Microsoft.OpenApi.Hidi.Tests", + "test/Microsoft.OpenApi.Tests", + "tool/Microsoft.OpenApi.Hidi.public.snk", + "tool/Microsoft.OpenApi.OData.public.snk", + "tool/hidi-local-nuget.config", + "Dockerfile", + ".dockerignore", + "install-tool.ps1", + "hidi-release-please-config.json", + ".hidi-release-please-manifest.json" ], "release-type": "simple", "bump-minor-pre-major": true, diff --git a/src/Microsoft.OpenApi.Hidi/CHANGELOG.md b/src/Microsoft.OpenApi.Hidi/CHANGELOG.md new file mode 100644 index 000000000..21c25cb3d --- /dev/null +++ b/src/Microsoft.OpenApi.Hidi/CHANGELOG.md @@ -0,0 +1,10 @@ +# Hidi changelog + +## 2.12.2 + +Migration baseline from OpenAPI.NET `support/v2`. This version is already +published and must not be republished from this repository. Earlier release +notes remain in [OpenAPI.NET releases](https://github.com/microsoft/OpenAPI.NET/releases). + +Hidi has its own release manifest and `hidi-v2.*` tags, independent of OData. +Destination release automation is disabled until the source publishing cutover. diff --git a/src/Microsoft.OpenApi.Hidi/CsdlFilter.xslt b/src/Microsoft.OpenApi.Hidi/CsdlFilter.xslt new file mode 100644 index 000000000..ee3bf0d40 --- /dev/null +++ b/src/Microsoft.OpenApi.Hidi/CsdlFilter.xslt @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.OpenApi.Hidi/Extensions/CommandExtensions.cs b/src/Microsoft.OpenApi.Hidi/Extensions/CommandExtensions.cs new file mode 100644 index 000000000..1a2dc477d --- /dev/null +++ b/src/Microsoft.OpenApi.Hidi/Extensions/CommandExtensions.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.CommandLine; + +namespace Microsoft.OpenApi.Hidi.Extensions +{ + internal static class CommandExtensions + { + public static void AddOptions(this Command command, IReadOnlyList