diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 00000000..ee0b6a3f --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,35 @@ +name: 'Build and Test' + +on: + workflow_dispatch: + inputs: + reason: + description: 'The reason for running the workflow' + required: false + default: 'Manual build and run tests' + workflow_run: + workflows: [ Build ] + types: + - completed + +jobs: + build: + uses: ./.github/workflows/build.yml + test: + name: Test - ${{matrix.os}} + needs: [build] + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ ubuntu-latest, windows-latest, macOS-latest ] + steps: + - name: Test + if: ${{ github.event.workflow_run.conclusion == 'success' }} + timeout-minutes: 60 + run: >- + dotnet test "${{ env.PROJECT_PATH }}" + --no-restore + --no-build + --verbosity normal + --logger "trx;LogFileName=test-results.trx" || true + \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..39c3dd2b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,86 @@ +name: 'Build' + +on: + workflow_dispatch: + inputs: + reason: + description: 'The reason for running the workflow' + required: false + default: 'Manual run' +# workflow_call: + push: +# paths: +# - '**.cs' +# - '**.csproj' + pull_request: + branches: [ master ] + paths: + - '**.cs' + - '**.csproj' + +env: + # Disable the .NET logo in the console output. + DOTNET_NOLOGO: true + + # Stop wasting time caching packages + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + + # Disable sending usage data to Microsoft + DOTNET_CLI_TELEMETRY_OPTOUT: true + + DOTNET_ADD_GLOBAL_TOOLS_TO_PATH: false + + DOTNET_MULTILEVEL_LOOKUP: 0 + + PROJECT_PATH: . + + CONFIGURATION: Release + + # Set the build number in MinVer. + MINVERBUILDMETADATA: build.${{github.run_number}} + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build ${{ matrix.os }} - dotnet ${{ matrix.dotnet }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest, windows-latest, macos-latest ] + + steps: + - name: Print manual run reason + if: ${{ github.event_name == 'workflow_dispatch' }} + run: | + echo 'Reason: ${{ github.event.inputs.reason }}' + + - name: Checkout + uses: actions/checkout@v4 + with: + lfs: true + fetch-depth: 0 + + - name: Setup .NET (global.json) + uses: actions/setup-dotnet@v4 + + - uses: actions/cache@v4 + with: + path: ~/.nuget/packages + # Look to see if there is a cache hit for the corresponding requirements file + key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }} + restore-keys: | + ${{ runner.os }}-nuget + + - name: Restore + run: dotnet restore "${{ env.PROJECT_PATH }}" + + - name: Build + run: >- + dotnet build "${{ env.PROJECT_PATH }}" + --configuration "${{ env.CONFIGURATION }}" + --no-restore + -p:ContinuousIntegrationBuild=true \ No newline at end of file diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml deleted file mode 100644 index 74fb1cfd..00000000 --- a/.github/workflows/ci-cd.yml +++ /dev/null @@ -1,117 +0,0 @@ - -name: "CI/CD" -on: - workflow_dispatch: - push: - branches: [ master ] - paths-ignore: - - '**/*.md' - - '**/*.gif' - - '**/*.png' - - '**/*.gitignore' - - '**/*.gitattributes' - - LICENSE - - tests/* - tags: - - '[0-9]+.[0-9]+.[0-9]+' - pull_request: - branches: [ master ] - paths-ignore: - - '**/*.md' - - '**/*.gif' - - '**/*.png' - - '**/*.gitignore' - - '**/*.gitattributes' - - LICENSE - - tests/* - -env: - # Disable the .NET logo in the console output. - DOTNET_NOLOGO: true - - # Stop wasting time caching packages - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true - - # Disable sending usage data to Microsoft - DOTNET_CLI_TELEMETRY_OPTOUT: true - - DOTNET_ADD_GLOBAL_TOOLS_TO_PATH: false - - DOTNET_MULTILEVEL_LOOKUP: 0 - - # Project name to pack and publish - PROJECT_NAME: redmine-net-api - - BUILD_CONFIGURATION: Release - - # Set the build number in MinVer. - MINVERBUILDMETADATA: build.${{github.run_number}} - -jobs: - build: - name: OS ${{ matrix.os }} - dotnet ${{ matrix.dotnet }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ ubuntu-latest, windows-latest, macos-latest ] - dotnet: [ '7.x.x' ] - - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - lfs: true - fetch-depth: 0 - - - name: Setup .NET Core - uses: actions/setup-dotnet@v3 - with: - dotnet-version: ${{ matrix.dotnet }} - - - name: Get Version - run: | - echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV - - - name: Restore - run: dotnet restore - - - name: Build - run: | - dotnet build --no-restore --configuration $BUILD_CONFIGURATION -p:Version=$VERSION - - - name: Build Signed - if: runner.os == 'Linux' - run: dotnet build redmine-net-api.sln --no-restore --configuration $BUILD_CONFIGURATION -p:Version=$VERSION -p:Sign=true - - - name: Test - run: dotnet test --no-restore --no-build --configuration $BUILD_CONFIGURATION - - - name: Pack && startsWith(github.ref, 'refs/tags') - if: runner.os == 'Linux' - run: | - dotnet pack ./src/redmine-net-api/redmine-net-api.csproj -o ./artifacts --configuration $BUILD_CONFIGURATION -p:Version=$VERSION --include-symbols --include-source -p:SymbolPackageFormat=snupkg - - - name: Pack Signed && startsWith(github.ref, 'refs/tags') - if: runner.os == 'Linux' - run: | - dotnet pack ./src/redmine-net-api/redmine-net-api.csproj -o ./artifacts --configuration $BUILD_CONFIGURATION -p:Version=$VERSION --include-symbols --include-source -p:Sign=true -p:SymbolPackageFormat=snupkg - - - uses: actions/upload-artifact@v3 - if: runner.os == 'Linux' - with: - name: artifacts - path: ./artifacts - - deploy: - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags') - needs: build - name: Deploy Packages - steps: - - uses: actions/download-artifact@v3 - with: - name: artifacts - path: ./artifacts - - - name: Publish packages - run: dotnet nuget push ./artifacts/**.nupkg --source nuget.org -k ${{secrets.NUGET_API_KEY}} \ No newline at end of file diff --git a/.github/workflows/pack.yml b/.github/workflows/pack.yml new file mode 100644 index 00000000..a813fb3d --- /dev/null +++ b/.github/workflows/pack.yml @@ -0,0 +1,103 @@ +name: 'Pack' + +on: + workflow_run: + workflows: [ 'Build' ] + types: [ requested ] + branches: [ master ] + + workflow_dispatch: + inputs: + reason: + description: 'The reason for running the workflow' + required: false + default: 'Manual pack' + version: + description: 'Version' + required: true + +env: + CONFIGURATION: 'Release' + + PROJECT_PATH: "." + + PROJECT_NAME: redmine-net-api + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + pack: + name: Pack + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + steps: + - name: Determine Version + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV + else + echo "VERSION=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_ENV + fi + echo "$GITHUB_ENV" + + - name: Print Version + run: | + echo "$VERSION" + + - name: Validate Version matches SemVer format + run: | + if [[ ! "$VERSION" =~ ^([0-9]+\.){2}[0-9]+$ ]]; then + echo "The version does not match the SemVer format (X.Y.Z). Please provide a valid version." + exit 1 + fi + + - name: Checkout + uses: actions/checkout@v4 + with: + lfs: true + fetch-depth: 0 + + - name: Setup .NET Core (global.json) + uses: actions/setup-dotnet@v4 + + - name: Install dependencies + run: dotnet restore "${{ env.PROJECT_PATH }}" + + - name: Pack + run: >- + dotnet pack ./src/redmine-net-api/redmine-net-api.csproj + --output ./artifacts + --configuration "${{ env.CONFIGURATION }}" + -p:Version=$VERSION + -p:PackageVersion=${{ env.VERSION }} + -p:SymbolPackageFormat=snupkg + + - name: Pack Signed + run: >- + dotnet pack ./src/redmine-net-api/redmine-net-api.csproj + --output ./artifacts + --configuration "${{ env.CONFIGURATION }}" + --include-symbols + --include-source + -p:Version=$VERSION + -p:PackageVersion=${{ env.VERSION }} + -p:SymbolPackageFormat=snupkg + -p:Sign=true + + - name: Install dotnet-validate + run: >- + dotnet tool install + --global dotnet-validate + --version 0.0.1-preview.304 + + - name: Validate NuGet package + run: | + dotnet-validate package local ./artifacts/**.nupkg + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: artifacts + path: ./artifacts diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..605ba8b3 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,47 @@ +name: 'Publish to NuGet' + +on: + workflow_dispatch: + inputs: + reason: + description: 'The reason for running the workflow' + required: false + default: 'Manual publish to nuget' + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + publish: + name: Publish to Nuget + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + steps: + - name: Print manual run reason + if: ${{ github.event_name == 'workflow_dispatch' }} + run: | + echo 'Reason: ${{ github.event.inputs.reason }}' + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: artifacts + path: ./artifacts + + - name: Publish packages + run: >- + dotnet nuget push ./artifacts/**.nupkg + --source 'https://api.nuget.org/v3/index.json' + --api-key ${{secrets.NUGET_TOKEN}} + --skip-duplicate + + - name: Upload artifacts to the GitHub release + uses: Roang-zero1/github-upload-release-artifacts-action@v3.0.0 + with: + args: ./artifacts + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index fb133faf..3d16a055 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 11 + 12 strict true diff --git a/docker-compose.yml b/docker-compose.yml index e7a53c7d..5a788f19 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: redmine: ports: - '8089:3000' - image: 'redmine:4.1.1-alpine' + image: 'redmine:5.1.1-alpine' container_name: 'redmine-web' depends_on: - db-postgres @@ -33,7 +33,7 @@ services: POSTGRES_USER: redmine-usr POSTGRES_PASSWORD: redmine-pswd container_name: 'redmine-db' - image: 'postgres:11.1-alpine' + image: 'postgres:16-alpine' healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 20s diff --git a/global.json b/global.json new file mode 100644 index 00000000..88e2f39a --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "8.0.101", + "allowPrerelease": false, + "rollForward": "latestMajor" + } +} \ No newline at end of file diff --git a/redmine-net-api.sln b/redmine-net-api.sln index 332b18aa..2715270a 100644 --- a/redmine-net-api.sln +++ b/redmine-net-api.sln @@ -12,22 +12,46 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "redmine-net-api.Tests", "te EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionFolder", "SolutionFolder", "{E8C35EC2-DD90-46E8-9B63-84EFD5F2FDE3}" ProjectSection(SolutionItems) = preProject - appveyor.yml = appveyor.yml CHANGELOG.md = CHANGELOG.md CONTRIBUTING.md = CONTRIBUTING.md - Directory.Build.props = Directory.Build.props - docker-compose.yml = docker-compose.yml ISSUE_TEMPLATE.md = ISSUE_TEMPLATE.md LICENSE = LICENSE - logo.png = logo.png PULL_REQUEST_TEMPLATE.md = PULL_REQUEST_TEMPLATE.md README.md = README.md - redmine-net-api.snk = redmine-net-api.snk + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitActions", "GitActions", "{79119F8B-C468-4DC8-BE6F-6E7102BD2079}" + ProjectSection(SolutionItems) = preProject + .github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml + .github\workflows\pack.yml = .github\workflows\pack.yml + .github\workflows\build.yml = .github\workflows\build.yml + .github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml + .github\workflows\publish.yml = .github\workflows\publish.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AppVeyor", "AppVeyor", "{F20AEA6C-B957-4A83-9616-B91548B4C561}" + ProjectSection(SolutionItems) = preProject + appveyor.yml = appveyor.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{707B6A3F-1A2C-4EFE-851F-1DB0E68CFFFB}" + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props releasenotes.props = releasenotes.props signing.props = signing.props version.props = version.props - .github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml - .github\workflows\ci-cd.yml = .github\workflows\ci-cd.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docker", "Docker", "{1D340EEB-C535-45D4-80D7-ADD4434D7B77}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Others", "Others", "{4ADECA2A-4D7B-4F05-85A2-0C0963A83689}" + ProjectSection(SolutionItems) = preProject + logo.png = logo.png + redmine-net-api.snk = redmine-net-api.snk + global.json = global.json EndProjectSection EndProject Global @@ -55,6 +79,11 @@ Global GlobalSection(NestedProjects) = preSolution {0E6B9B72-445D-4E71-8D29-48C4A009AB03} = {0DFF4758-5C19-4D8F-BA6C-76E618323F6A} {900EF0B3-0233-45DA-811F-4C59483E8452} = {F3F4278D-6271-4F77-BA88-41555D53CBD1} + {79119F8B-C468-4DC8-BE6F-6E7102BD2079} = {E8C35EC2-DD90-46E8-9B63-84EFD5F2FDE3} + {F20AEA6C-B957-4A83-9616-B91548B4C561} = {E8C35EC2-DD90-46E8-9B63-84EFD5F2FDE3} + {707B6A3F-1A2C-4EFE-851F-1DB0E68CFFFB} = {E8C35EC2-DD90-46E8-9B63-84EFD5F2FDE3} + {1D340EEB-C535-45D4-80D7-ADD4434D7B77} = {E8C35EC2-DD90-46E8-9B63-84EFD5F2FDE3} + {4ADECA2A-4D7B-4F05-85A2-0C0963A83689} = {E8C35EC2-DD90-46E8-9B63-84EFD5F2FDE3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4AA87D90-ABD0-4793-BE47-955B35FAE2BB} diff --git a/src/redmine-net-api/Extensions/RedmineManagerExtensions.cs b/src/redmine-net-api/Extensions/RedmineManagerExtensions.cs index 043404d9..75a778df 100644 --- a/src/redmine-net-api/Extensions/RedmineManagerExtensions.cs +++ b/src/redmine-net-api/Extensions/RedmineManagerExtensions.cs @@ -18,7 +18,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; -using System.Net; #if !(NET20) using System.Threading; using System.Threading.Tasks; @@ -391,7 +390,7 @@ public static async Task> GetProjectNewsAsync(this RedmineMan var response = await redmineManager.ApiClient.GetPagedAsync(escapedUri, requestOptions, cancellationToken).ConfigureAwait(false); - return response.DeserializeToPagedResults(redmineManager.Serializer);; + return response.DeserializeToPagedResults(redmineManager.Serializer); } /// @@ -442,7 +441,7 @@ public static async Task> GetProjectMembershipsA var response = await redmineManager.ApiClient.GetPagedAsync(uri, requestOptions, cancellationToken).ConfigureAwait(false); - return response.DeserializeToPagedResults(redmineManager.Serializer);; + return response.DeserializeToPagedResults(redmineManager.Serializer); } /// @@ -460,7 +459,7 @@ public static async Task> GetProjectFilesAsync(this RedmineMa var response = await redmineManager.ApiClient.GetPagedAsync(uri, requestOptions, cancellationToken).ConfigureAwait(false); - return response.DeserializeToPagedResults(redmineManager.Serializer);; + return response.DeserializeToPagedResults(redmineManager.Serializer); } diff --git a/src/redmine-net-api/Types/Detail.cs b/src/redmine-net-api/Types/Detail.cs index 298e93b7..9d74336a 100644 --- a/src/redmine-net-api/Types/Detail.cs +++ b/src/redmine-net-api/Types/Detail.cs @@ -173,10 +173,10 @@ public void ReadJson(JsonReader reader) public bool Equals(Detail other) { if (other == null) return false; - return string.Equals(Property, other.Property, StringComparison.InvariantCultureIgnoreCase) - && string.Equals(Name, other.Name, StringComparison.InvariantCultureIgnoreCase) - && string.Equals(OldValue, other.OldValue, StringComparison.InvariantCultureIgnoreCase) - && string.Equals(NewValue, other.NewValue, StringComparison.InvariantCultureIgnoreCase); + return string.Equals(Property, other.Property, StringComparison.OrdinalIgnoreCase) + && string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase) + && string.Equals(OldValue, other.OldValue, StringComparison.OrdinalIgnoreCase) + && string.Equals(NewValue, other.NewValue, StringComparison.OrdinalIgnoreCase); } /// diff --git a/src/redmine-net-api/Types/Error.cs b/src/redmine-net-api/Types/Error.cs index ef6b7b7e..96621f51 100644 --- a/src/redmine-net-api/Types/Error.cs +++ b/src/redmine-net-api/Types/Error.cs @@ -119,7 +119,7 @@ public bool Equals(Error other) { if (other == null) return false; - return string.Equals(Info,other.Info, StringComparison.InvariantCultureIgnoreCase); + return string.Equals(Info,other.Info, StringComparison.OrdinalIgnoreCase); } /// diff --git a/src/redmine-net-api/Types/Search.cs b/src/redmine-net-api/Types/Search.cs index d9355764..9f35e29a 100644 --- a/src/redmine-net-api/Types/Search.cs +++ b/src/redmine-net-api/Types/Search.cs @@ -123,10 +123,10 @@ public void ReadJson(JsonReader reader) public bool Equals(Search other) { if (other == null) return false; - return Id == other.Id && string.Equals(Title, other.Title, StringComparison.InvariantCultureIgnoreCase) - && string.Equals(Description, other.Description, StringComparison.InvariantCultureIgnoreCase) - && string.Equals(Url, other.Url, StringComparison.InvariantCultureIgnoreCase) - && string.Equals(Type, other.Type, StringComparison.InvariantCultureIgnoreCase) + return Id == other.Id && string.Equals(Title, other.Title, StringComparison.OrdinalIgnoreCase) + && string.Equals(Description, other.Description, StringComparison.OrdinalIgnoreCase) + && string.Equals(Url, other.Url, StringComparison.OrdinalIgnoreCase) + && string.Equals(Type, other.Type, StringComparison.OrdinalIgnoreCase) && DateTime == other.DateTime; } diff --git a/src/redmine-net-api/Types/Version.cs b/src/redmine-net-api/Types/Version.cs index ca0ed402..a9c74321 100644 --- a/src/redmine-net-api/Types/Version.cs +++ b/src/redmine-net-api/Types/Version.cs @@ -1,4 +1,4 @@ -/* +/* Copyright 2011 - 2023 Adrian Popescu Licensed under the Apache License, Version 2.0 (the "License"); @@ -232,7 +232,7 @@ public override bool Equals(Version other) && CreatedOn == other.CreatedOn && UpdatedOn == other.UpdatedOn && (CustomFields != null ? CustomFields.Equals(other.CustomFields) : other.CustomFields == null) - && string.Equals(WikiPageTitle,other.WikiPageTitle, StringComparison.InvariantCultureIgnoreCase) + && string.Equals(WikiPageTitle,other.WikiPageTitle, StringComparison.OrdinalIgnoreCase) && EstimatedHours == other.EstimatedHours && SpentHours == other.SpentHours ; diff --git a/src/redmine-net-api/redmine-net-api.csproj b/src/redmine-net-api/redmine-net-api.csproj index e6e154d0..cc750f06 100644 --- a/src/redmine-net-api/redmine-net-api.csproj +++ b/src/redmine-net-api/redmine-net-api.csproj @@ -68,7 +68,6 @@ -