From fc9c1a5d1e474f1a2d94995dcb876f19909b42fd Mon Sep 17 00:00:00 2001 From: hpractv Date: Mon, 11 May 2026 09:45:50 -0600 Subject: [PATCH 1/6] feat: enhance solution structure and CI workflows - Added new projects: `analyticsLibrary.ReleaseTooling`, `analyticsLibrary.NuGetMaintenance`, and their corresponding test projects to the solution. - Updated `Directory.Packages.props` to include `NuGet.Versioning` package. - Improved CI workflows by adding a linting job for GitHub Actions and refining the release process to include deprecation of older package versions. - Updated README to clarify the stable release process and CI configurations. --- .github/workflows/ci.yml | 32 ++- .github/workflows/release.yml | 204 +++--------------- Analytics Library.sln | 137 ++++++++++++ Directory.Packages.props | 1 + README.md | 20 +- .../FlatContainerIndexParser.cs | 30 +++ .../NuGetFlatContainerHttp.cs | 24 +++ .../NuGetGalleryDeprecationApi.cs | 62 ++++++ .../ReleaseConstants.cs | 23 ++ .../VersionDeprecationPlanner.cs | 39 ++++ .../analyticsLibrary.ReleaseTooling.csproj | 12 ++ .../FlatContainerIndexParserTests.cs | 26 +++ .../GlobalUsings.cs | 1 + .../NuGetFlatContainerIntegrationTests.cs | 34 +++ .../NuGetGalleryDeprecationApiTests.cs | 28 +++ .../VersionDeprecationPlannerTests.cs | 33 +++ ...alyticsLibrary.ReleaseTooling.Tests.csproj | 24 +++ .../Program.cs | 160 ++++++++++++++ .../analyticsLibrary.NuGetMaintenance.csproj | 13 ++ 19 files changed, 718 insertions(+), 185 deletions(-) create mode 100644 src/analyticsLibrary.ReleaseTooling/FlatContainerIndexParser.cs create mode 100644 src/analyticsLibrary.ReleaseTooling/NuGetFlatContainerHttp.cs create mode 100644 src/analyticsLibrary.ReleaseTooling/NuGetGalleryDeprecationApi.cs create mode 100644 src/analyticsLibrary.ReleaseTooling/ReleaseConstants.cs create mode 100644 src/analyticsLibrary.ReleaseTooling/VersionDeprecationPlanner.cs create mode 100644 src/analyticsLibrary.ReleaseTooling/analyticsLibrary.ReleaseTooling.csproj create mode 100644 tests/analyticsLibrary.ReleaseTooling.Tests/FlatContainerIndexParserTests.cs create mode 100644 tests/analyticsLibrary.ReleaseTooling.Tests/GlobalUsings.cs create mode 100644 tests/analyticsLibrary.ReleaseTooling.Tests/NuGetFlatContainerIntegrationTests.cs create mode 100644 tests/analyticsLibrary.ReleaseTooling.Tests/NuGetGalleryDeprecationApiTests.cs create mode 100644 tests/analyticsLibrary.ReleaseTooling.Tests/VersionDeprecationPlannerTests.cs create mode 100644 tests/analyticsLibrary.ReleaseTooling.Tests/analyticsLibrary.ReleaseTooling.Tests.csproj create mode 100644 tools/analyticsLibrary.NuGetMaintenance/Program.cs create mode 100644 tools/analyticsLibrary.NuGetMaintenance/analyticsLibrary.NuGetMaintenance.csproj diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9d3b2f..fc50e73 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,15 @@ on: branches: [ main ] jobs: + workflow-lint: + name: Lint GitHub Actions workflows + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: actionlint + uses: rhysd/actionlint@v1.7.12 + changes: name: Detect package-impacting changes runs-on: ubuntu-latest @@ -33,6 +42,12 @@ jobs: workflows: - '.github/workflows/ci.yml' - '.github/workflows/release.yml' + tooling: + - 'Directory.Build.props' + - 'Directory.Packages.props' + - 'src/analyticsLibrary.ReleaseTooling/**' + - 'tools/analyticsLibrary.NuGetMaintenance/**' + - 'tests/analyticsLibrary.ReleaseTooling.Tests/**' core: - 'src/analyticsLibrary.Core/**' algorithms: @@ -51,6 +66,13 @@ jobs: - name: Compute effective package flags id: flags run: | + if [ '${{ steps.filter.outputs.tooling }}' = "true" ]; then + for n in core_changed algorithms_changed statistics_changed excel_changed access_changed hadoop_changed root_changed any_changed; do + echo "${n}=true" >> "$GITHUB_OUTPUT" + done + exit 0 + fi + WORKFLOWS_CHANGED='${{ steps.filter.outputs.workflows }}' set_flag() { local name="$1" @@ -86,7 +108,9 @@ jobs: no-package-changes: name: No package-impacting changes runs-on: ubuntu-latest - needs: changes + needs: + - changes + - workflow-lint if: needs.changes.outputs.any_changed != 'true' steps: - name: Skip message @@ -95,7 +119,9 @@ jobs: build-test-pack: name: Build, Test, and Pack runs-on: ubuntu-latest - needs: changes + needs: + - changes + - workflow-lint if: needs.changes.outputs.any_changed == 'true' steps: @@ -180,7 +206,7 @@ jobs: local package_name="$2" if [ "$should_pack" = "true" ]; then EXPECTED=$((EXPECTED+1)) - if ! ls ./artifacts/${package_name}.*.nupkg 1>/dev/null 2>&1; then + if ! ls "./artifacts/${package_name}."*.nupkg 1>/dev/null 2>&1; then echo "ERROR: Expected package not found: ${package_name}" exit 1 fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d923308..7f5203b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,118 +12,10 @@ on: default: '2.0.0' jobs: - changes: - name: Detect package-impacting changes - runs-on: ubuntu-latest - outputs: - core_changed: ${{ steps.flags.outputs.core_changed }} - algorithms_changed: ${{ steps.flags.outputs.algorithms_changed }} - statistics_changed: ${{ steps.flags.outputs.statistics_changed }} - excel_changed: ${{ steps.flags.outputs.excel_changed }} - access_changed: ${{ steps.flags.outputs.access_changed }} - hadoop_changed: ${{ steps.flags.outputs.hadoop_changed }} - root_changed: ${{ steps.flags.outputs.root_changed }} - any_changed: ${{ steps.flags.outputs.any_changed }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Resolve comparison base - id: base - run: | - if [ "${{ github.event_name }}" = "push" ]; then - BASE="${{ github.event.before }}" - if [ -n "$BASE" ] && [ "$BASE" != "0000000000000000000000000000000000000000" ]; then - echo "base_ref=$BASE" >> "$GITHUB_OUTPUT" - exit 0 - fi - fi - - PREV_TAG="$(git tag --sort=-creatordate | sed -n '2p')" - if [ -z "$PREV_TAG" ]; then - BASE="$(git rev-list --max-parents=0 HEAD | sed -n '1p')" - else - BASE="$(git rev-list -n 1 "$PREV_TAG")" - fi - echo "base_ref=$BASE" >> "$GITHUB_OUTPUT" - - - name: Detect changed paths - id: filter - uses: dorny/paths-filter@v3 - with: - base: ${{ steps.base.outputs.base_ref }} - ref: ${{ github.sha }} - filters: | - workflows: - - '.github/workflows/ci.yml' - - '.github/workflows/release.yml' - core: - - 'src/analyticsLibrary.Core/**' - algorithms: - - 'src/analyticsLibrary.Algorithms/**' - statistics: - - 'src/analyticsLibrary.Statistics/**' - excel: - - 'src/analyticsLibrary.Excel/**' - access: - - 'src/analyticsLibrary.Access/**' - hadoop: - - 'src/analyticsLibrary.Hadoop/**' - root: - - 'src/analyticsLibrary/**' - - - name: Compute effective package flags - id: flags - run: | - WORKFLOWS_CHANGED='${{ steps.filter.outputs.workflows }}' - set_flag() { - local name="$1" - local raw="$2" - if [ "$raw" = "true" ] || [ "$WORKFLOWS_CHANGED" = "true" ]; then - echo "${name}=true" >> "$GITHUB_OUTPUT" - else - echo "${name}=false" >> "$GITHUB_OUTPUT" - fi - } - - set_flag core_changed '${{ steps.filter.outputs.core }}' - set_flag algorithms_changed '${{ steps.filter.outputs.algorithms }}' - set_flag statistics_changed '${{ steps.filter.outputs.statistics }}' - set_flag excel_changed '${{ steps.filter.outputs.excel }}' - set_flag access_changed '${{ steps.filter.outputs.access }}' - set_flag hadoop_changed '${{ steps.filter.outputs.hadoop }}' - set_flag root_changed '${{ steps.filter.outputs.root }}' - - if [ '${{ steps.filter.outputs.core }}' = "true" ] || \ - [ '${{ steps.filter.outputs.algorithms }}' = "true" ] || \ - [ '${{ steps.filter.outputs.statistics }}' = "true" ] || \ - [ '${{ steps.filter.outputs.excel }}' = "true" ] || \ - [ '${{ steps.filter.outputs.access }}' = "true" ] || \ - [ '${{ steps.filter.outputs.hadoop }}' = "true" ] || \ - [ '${{ steps.filter.outputs.root }}' = "true" ] || \ - [ "$WORKFLOWS_CHANGED" = "true" ]; then - echo "any_changed=true" >> "$GITHUB_OUTPUT" - else - echo "any_changed=false" >> "$GITHUB_OUTPUT" - fi - - no-package-changes: - name: No package-impacting changes - runs-on: ubuntu-latest - needs: changes - if: needs.changes.outputs.any_changed != 'true' - steps: - - name: Skip message - run: echo "No package-impacting changes detected since the release baseline; skipping NuGet release." - release: - name: Build, Test, Pack, and Deploy + name: Build, Test, Pack, Deploy, and Deprecate runs-on: ubuntu-latest environment: nuget-release - needs: changes - if: needs.changes.outputs.any_changed == 'true' steps: - name: Checkout @@ -140,10 +32,9 @@ jobs: if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then VERSION="${{ github.event.inputs.version }}" else - # Strip the leading 'v' from tag name (e.g. v2.0.0 -> 2.0.0) VERSION="${GITHUB_REF_NAME#v}" fi - echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT" echo "Release version: $VERSION" - name: Restore @@ -155,80 +46,33 @@ jobs: - name: Test run: dotnet test -c Release --no-build --verbosity normal - - name: Pack + - name: Pack all packages run: | mkdir -p ./artifacts - PACKED=0 - - if [ "${{ needs.changes.outputs.core_changed }}" = "true" ]; then - dotnet pack src/analyticsLibrary.Core/analyticsLibrary.Core.csproj -c Release --no-build -o ./artifacts - PACKED=$((PACKED+1)) - fi - if [ "${{ needs.changes.outputs.algorithms_changed }}" = "true" ]; then - dotnet pack src/analyticsLibrary.Algorithms/analyticsLibrary.Algorithms.csproj -c Release --no-build -o ./artifacts - PACKED=$((PACKED+1)) - fi - if [ "${{ needs.changes.outputs.statistics_changed }}" = "true" ]; then - dotnet pack src/analyticsLibrary.Statistics/analyticsLibrary.Statistics.csproj -c Release --no-build -o ./artifacts - PACKED=$((PACKED+1)) - fi - if [ "${{ needs.changes.outputs.excel_changed }}" = "true" ]; then - dotnet pack src/analyticsLibrary.Excel/analyticsLibrary.Excel.csproj -c Release --no-build -o ./artifacts - PACKED=$((PACKED+1)) - fi - if [ "${{ needs.changes.outputs.access_changed }}" = "true" ]; then - dotnet pack src/analyticsLibrary.Access/analyticsLibrary.Access.csproj -c Release --no-build -o ./artifacts - PACKED=$((PACKED+1)) - fi - if [ "${{ needs.changes.outputs.hadoop_changed }}" = "true" ]; then - dotnet pack src/analyticsLibrary.Hadoop/analyticsLibrary.Hadoop.csproj -c Release --no-build -o ./artifacts - PACKED=$((PACKED+1)) - fi - if [ "${{ needs.changes.outputs.root_changed }}" = "true" ]; then - dotnet pack src/analyticsLibrary/analyticsLibrary.csproj -c Release --no-build -o ./artifacts - PACKED=$((PACKED+1)) - fi - - if [ "$PACKED" -eq 0 ]; then - echo "No package projects selected for packing." - exit 1 - fi + V="${{ steps.version.outputs.VERSION }}" + dotnet pack src/analyticsLibrary.Core/analyticsLibrary.Core.csproj -c Release --no-build -p:Version=$V -o ./artifacts + dotnet pack src/analyticsLibrary.Algorithms/analyticsLibrary.Algorithms.csproj -c Release --no-build -p:Version=$V -o ./artifacts + dotnet pack src/analyticsLibrary.Statistics/analyticsLibrary.Statistics.csproj -c Release --no-build -p:Version=$V -o ./artifacts + dotnet pack src/analyticsLibrary.Excel/analyticsLibrary.Excel.csproj -c Release --no-build -p:Version=$V -o ./artifacts + dotnet pack src/analyticsLibrary.Access/analyticsLibrary.Access.csproj -c Release --no-build -p:Version=$V -o ./artifacts + dotnet pack src/analyticsLibrary.Hadoop/analyticsLibrary.Hadoop.csproj -c Release --no-build -p:Version=$V -o ./artifacts + dotnet pack src/analyticsLibrary/analyticsLibrary.csproj -c Release --no-build -p:Version=$V -o ./artifacts - name: Inspect packages run: | echo "Produced packages:" ls -la ./artifacts/*.nupkg - EXPECTED=0 - MISSING=0 - verify_pkg() { - local should_pack="$1" - local package_name="$2" - if [ "$should_pack" = "true" ]; then - EXPECTED=$((EXPECTED+1)) - if ! ls ./artifacts/${package_name}.*.nupkg 1>/dev/null 2>&1; then - echo "ERROR: Expected package not found: ${package_name}" - MISSING=$((MISSING+1)) - fi + for id in analyticsLibrary.Core analyticsLibrary.Algorithms analyticsLibrary.Statistics analyticsLibrary.Excel analyticsLibrary.Access analyticsLibrary.Hadoop; do + if ! ls "./artifacts/${id}."*.nupkg 1>/dev/null 2>&1; then + echo "ERROR: Expected package not found: ${id}" + exit 1 fi - } - - verify_pkg "${{ needs.changes.outputs.core_changed }}" "analyticsLibrary.Core" - verify_pkg "${{ needs.changes.outputs.algorithms_changed }}" "analyticsLibrary.Algorithms" - verify_pkg "${{ needs.changes.outputs.statistics_changed }}" "analyticsLibrary.Statistics" - verify_pkg "${{ needs.changes.outputs.excel_changed }}" "analyticsLibrary.Excel" - verify_pkg "${{ needs.changes.outputs.access_changed }}" "analyticsLibrary.Access" - verify_pkg "${{ needs.changes.outputs.hadoop_changed }}" "analyticsLibrary.Hadoop" - verify_pkg "${{ needs.changes.outputs.root_changed }}" "analyticsLibrary" - - if [ "$EXPECTED" -eq 0 ]; then - echo "Aborting: No packages were expected after change detection." + done + if ! ls ./artifacts/analyticsLibrary.[0-9]*.nupkg 1>/dev/null 2>&1; then + echo "ERROR: Expected root package not found: analyticsLibrary..nupkg" exit 1 fi - if [ "$MISSING" -gt 0 ]; then - echo "Aborting: $MISSING expected package(s) are missing." - exit 1 - fi - echo "All expected packages found and ready for deployment." + echo "All ${{ steps.version.outputs.VERSION }} packages found and ready for deployment." - name: Upload release artifacts uses: actions/upload-artifact@v4 @@ -243,3 +87,13 @@ jobs: --api-key ${{ secrets.NUGET_API_KEY }} \ --source https://api.nuget.org/v3/index.json \ --skip-duplicate + + - name: Deprecate prior versions on NuGet.org + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: | + if [ -z "$NUGET_API_KEY" ]; then + echo "NUGET_API_KEY is not set; skipping deprecation." + exit 0 + fi + dotnet run -c Release --no-build --project tools/analyticsLibrary.NuGetMaintenance -- apply-deprecations "${{ steps.version.outputs.VERSION }}" diff --git a/Analytics Library.sln b/Analytics Library.sln index 75a7170..0741ad0 100755 --- a/Analytics Library.sln +++ b/Analytics Library.sln @@ -29,6 +29,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "analyticsLibrary.Algorithms EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "analyticsLibrary.Excel.Tests", "tests\analyticsLibrary.Excel.Tests\analyticsLibrary.Excel.Tests.csproj", "{660F8C89-9149-452D-9E97-222E18D769D3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "analyticsLibrary.ReleaseTooling", "src\analyticsLibrary.ReleaseTooling\analyticsLibrary.ReleaseTooling.csproj", "{D5B7B9CE-2BF9-44D1-991F-8D175B183A90}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{07C2787E-EAC7-C090-1BA3-A61EC2A24D84}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "analyticsLibrary.NuGetMaintenance", "tools\analyticsLibrary.NuGetMaintenance\analyticsLibrary.NuGetMaintenance.csproj", "{1AAC51AF-5725-4377-8B33-0C094DE34B17}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "analyticsLibrary.ReleaseTooling.Tests", "tests\analyticsLibrary.ReleaseTooling.Tests\analyticsLibrary.ReleaseTooling.Tests.csproj", "{9BB1015E-6FA9-4734-9527-23ACB23898C2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution CD_ROM|Any CPU = CD_ROM|Any CPU @@ -516,6 +524,132 @@ Global {660F8C89-9149-452D-9E97-222E18D769D3}.SingleImage|x64.Build.0 = Debug|Any CPU {660F8C89-9149-452D-9E97-222E18D769D3}.SingleImage|x86.ActiveCfg = Debug|Any CPU {660F8C89-9149-452D-9E97-222E18D769D3}.SingleImage|x86.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.CD_ROM|Any CPU.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.CD_ROM|Any CPU.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.CD_ROM|x64.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.CD_ROM|x64.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.CD_ROM|x86.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.CD_ROM|x86.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Debug x86|Any CPU.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Debug x86|Any CPU.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Debug x86|x64.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Debug x86|x64.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Debug x86|x86.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Debug x86|x86.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Debug|x64.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Debug|x64.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Debug|x86.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Debug|x86.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.DVD-5|Any CPU.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.DVD-5|Any CPU.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.DVD-5|x64.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.DVD-5|x64.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.DVD-5|x86.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.DVD-5|x86.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Release x86|Any CPU.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Release x86|Any CPU.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Release x86|x64.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Release x86|x64.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Release x86|x86.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Release x86|x86.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Release|Any CPU.Build.0 = Release|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Release|x64.ActiveCfg = Release|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Release|x64.Build.0 = Release|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Release|x86.ActiveCfg = Release|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.Release|x86.Build.0 = Release|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.SingleImage|Any CPU.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.SingleImage|Any CPU.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.SingleImage|x64.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.SingleImage|x64.Build.0 = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.SingleImage|x86.ActiveCfg = Debug|Any CPU + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90}.SingleImage|x86.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.CD_ROM|Any CPU.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.CD_ROM|Any CPU.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.CD_ROM|x64.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.CD_ROM|x64.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.CD_ROM|x86.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.CD_ROM|x86.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Debug x86|Any CPU.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Debug x86|Any CPU.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Debug x86|x64.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Debug x86|x64.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Debug x86|x86.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Debug x86|x86.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Debug|x64.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Debug|x64.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Debug|x86.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Debug|x86.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.DVD-5|Any CPU.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.DVD-5|Any CPU.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.DVD-5|x64.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.DVD-5|x64.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.DVD-5|x86.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.DVD-5|x86.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Release x86|Any CPU.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Release x86|Any CPU.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Release x86|x64.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Release x86|x64.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Release x86|x86.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Release x86|x86.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Release|Any CPU.Build.0 = Release|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Release|x64.ActiveCfg = Release|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Release|x64.Build.0 = Release|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Release|x86.ActiveCfg = Release|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.Release|x86.Build.0 = Release|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.SingleImage|Any CPU.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.SingleImage|Any CPU.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.SingleImage|x64.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.SingleImage|x64.Build.0 = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.SingleImage|x86.ActiveCfg = Debug|Any CPU + {1AAC51AF-5725-4377-8B33-0C094DE34B17}.SingleImage|x86.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.CD_ROM|Any CPU.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.CD_ROM|Any CPU.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.CD_ROM|x64.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.CD_ROM|x64.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.CD_ROM|x86.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.CD_ROM|x86.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Debug x86|Any CPU.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Debug x86|Any CPU.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Debug x86|x64.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Debug x86|x64.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Debug x86|x86.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Debug x86|x86.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Debug|x64.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Debug|x64.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Debug|x86.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Debug|x86.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.DVD-5|Any CPU.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.DVD-5|Any CPU.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.DVD-5|x64.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.DVD-5|x64.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.DVD-5|x86.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.DVD-5|x86.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Release x86|Any CPU.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Release x86|Any CPU.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Release x86|x64.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Release x86|x64.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Release x86|x86.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Release x86|x86.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Release|Any CPU.Build.0 = Release|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Release|x64.ActiveCfg = Release|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Release|x64.Build.0 = Release|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Release|x86.ActiveCfg = Release|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.Release|x86.Build.0 = Release|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.SingleImage|Any CPU.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.SingleImage|Any CPU.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.SingleImage|x64.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.SingleImage|x64.Build.0 = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.SingleImage|x86.ActiveCfg = Debug|Any CPU + {9BB1015E-6FA9-4734-9527-23ACB23898C2}.SingleImage|x86.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -532,6 +666,9 @@ Global {23A3F035-185E-485F-80D8-DCBBDBD6CFA9} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {1C80F5C6-8A99-4941-8A81-97A28C60C3E8} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {660F8C89-9149-452D-9E97-222E18D769D3} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {D5B7B9CE-2BF9-44D1-991F-8D175B183A90} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {1AAC51AF-5725-4377-8B33-0C094DE34B17} = {07C2787E-EAC7-C090-1BA3-A61EC2A24D84} + {9BB1015E-6FA9-4734-9527-23ACB23898C2} = {0AB3BF05-4346-4AA6-1389-037BE0695223} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {52B0CB57-8044-4EA5-BC6B-F17324054845} diff --git a/Directory.Packages.props b/Directory.Packages.props index 32f3799..3caa684 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -27,5 +27,6 @@ + \ No newline at end of file diff --git a/README.md b/README.md index f4a9e7a..1ad77c8 100644 --- a/README.md +++ b/README.md @@ -249,18 +249,24 @@ dotnet pack -c Release -o ./nupkgs **Beta packages (pull requests)** -Opening or updating a pull request against `main` triggers `ci.yml`. The workflow builds, tests, packs beta packages versioned `2.0.0-beta.pr-.`, and uploads them as workflow artifacts. Beta packages are only pushed to NuGet.org when the `NUGET_API_KEY` secret is available and the PR is not from a fork. +Opening or updating a pull request against `main` triggers `ci.yml`. The workflow builds, tests, packs only the package projects whose `src/analyticsLibrary.*` paths changed (or when central/tooling paths change—see workflow filters), using beta versions read from each `.csproj` `` plus `-beta.pr-.`. It uploads `.nupkg` files as workflow artifacts. Beta packages are pushed to NuGet.org only on the `pull_request` event when `NUGET_API_KEY` is set and the PR branch is from the same repository (not a fork). Merging the PR to `main` does **not** publish betas to NuGet.org. -**Release packages** +**Stable release packages** -Push a version tag (e.g. `v2.0.0`) or trigger `release.yml` manually via workflow dispatch. The workflow: -1. Builds and tests the solution from the tagged commit. -2. Packs all seven packages. -3. Verifies all expected `.nupkg` files are present. -4. Deploys to NuGet.org under the `nuget-release` GitHub Environment, which requires explicit approval before the deploy step runs. +Publishing a stable release to NuGet.org is **not** triggered by merging a pull request alone. You must either push a SemVer tag matching `v*.*.*` (for example `v3.0.0`) at the commit you want to ship, or run `release.yml` manually via **workflow dispatch** and enter the version (for example `3.0.0`). + +The `release.yml` workflow then: + +1. Builds and tests the full solution (including release-tooling tests under `tests/analyticsLibrary.ReleaseTooling.Tests/`). +2. Packs **all seven** packages at the same release version (no path-based skipping). +3. Verifies every expected `.nupkg` is present. +4. Pushes packages to NuGet.org. This step uses the `nuget-release` GitHub Environment so you can require manual approval before anything is published. +5. Deprecates previously published versions on NuGet.org: all CI beta builds whose versions contain `-beta.pr-` are marked with an “other” deprecation reason and a testing-only message; every older **stable** version below the version you just shipped is marked **legacy** with the message: `This package is legacy and is no longer maintained`. The deprecation client calls NuGet.org’s HTTP API (`PUT https://www.nuget.org/api/v2/package/{packageId}/deprecations`) using the same `NUGET_API_KEY`. The key must include permission to **push** and **unlist** (deprecation uses the unlist scope on nuget.org). If NuGet.org returns **403** for deprecation, the account may not yet have the manage-deprecation API enabled for API keys; in that case you can deprecate versions manually on nuget.org until the API is available. Store the NuGet API key as a repository secret named `NUGET_API_KEY`. Never commit credentials. +CI also runs [actionlint](https://github.com/rhysd/actionlint) on workflow files. + --- ## Contributing diff --git a/src/analyticsLibrary.ReleaseTooling/FlatContainerIndexParser.cs b/src/analyticsLibrary.ReleaseTooling/FlatContainerIndexParser.cs new file mode 100644 index 0000000..5efa69c --- /dev/null +++ b/src/analyticsLibrary.ReleaseTooling/FlatContainerIndexParser.cs @@ -0,0 +1,30 @@ +using System.Text.Json; + +namespace analyticsLibrary.ReleaseTooling; + +public static class FlatContainerIndexParser +{ + public static IReadOnlyList ParseVersions(string json) + { + using var doc = JsonDocument.Parse(json); + if (!doc.RootElement.TryGetProperty("versions", out var versions) || versions.ValueKind != JsonValueKind.Array) + { + return Array.Empty(); + } + + var list = new List(); + foreach (var el in versions.EnumerateArray()) + { + if (el.ValueKind == JsonValueKind.String) + { + var s = el.GetString(); + if (!string.IsNullOrEmpty(s)) + { + list.Add(s); + } + } + } + + return list; + } +} diff --git a/src/analyticsLibrary.ReleaseTooling/NuGetFlatContainerHttp.cs b/src/analyticsLibrary.ReleaseTooling/NuGetFlatContainerHttp.cs new file mode 100644 index 0000000..c9759fc --- /dev/null +++ b/src/analyticsLibrary.ReleaseTooling/NuGetFlatContainerHttp.cs @@ -0,0 +1,24 @@ +namespace analyticsLibrary.ReleaseTooling; + +public static class NuGetFlatContainerHttp +{ + public static Uri GetPackageIndexUri(string packageId) => + new($"https://api.nuget.org/v3-flatcontainer/{packageId.ToLowerInvariant()}/index.json", UriKind.Absolute); + + public static async Task> GetPublishedVersionsAsync( + HttpClient http, + string packageId, + CancellationToken cancellationToken = default) + { + var uri = GetPackageIndexUri(packageId); + using var response = await http.GetAsync(uri, cancellationToken).ConfigureAwait(false); + if (response.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return Array.Empty(); + } + + response.EnsureSuccessStatusCode(); + var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + return FlatContainerIndexParser.ParseVersions(json); + } +} diff --git a/src/analyticsLibrary.ReleaseTooling/NuGetGalleryDeprecationApi.cs b/src/analyticsLibrary.ReleaseTooling/NuGetGalleryDeprecationApi.cs new file mode 100644 index 0000000..2d26ae0 --- /dev/null +++ b/src/analyticsLibrary.ReleaseTooling/NuGetGalleryDeprecationApi.cs @@ -0,0 +1,62 @@ +namespace analyticsLibrary.ReleaseTooling; + +public static class NuGetGalleryDeprecationApi +{ + public const string DeprecationEndpointTemplate = "https://www.nuget.org/api/v2/package/{0}/deprecations"; + + public static HttpRequestMessage CreateDeprecationRequest( + string packageId, + IReadOnlyList versions, + bool isLegacy, + bool hasCriticalBugs, + bool isOther, + string? customMessage, + string alternatePackageId, + string alternatePackageVersion, + string apiKey) + { + if (versions.Count == 0) + { + throw new ArgumentException("At least one version is required.", nameof(versions)); + } + + var pairs = new List>(); + for (var i = 0; i < versions.Count; i++) + { + pairs.Add(new KeyValuePair($"versions[{i}]", versions[i])); + } + + if (isLegacy) + { + pairs.Add(new KeyValuePair("isLegacy", "true")); + } + + if (hasCriticalBugs) + { + pairs.Add(new KeyValuePair("hasCriticalBugs", "true")); + } + + if (isOther) + { + pairs.Add(new KeyValuePair("isOther", "true")); + } + + if (!string.IsNullOrEmpty(customMessage)) + { + pairs.Add(new KeyValuePair("message", customMessage)); + } + + pairs.Add(new KeyValuePair("alternatePackageId", alternatePackageId)); + pairs.Add(new KeyValuePair("alternatePackageVersion", alternatePackageVersion)); + pairs.Add(new KeyValuePair("listedVerb", "Unchanged")); + + var url = string.Format(DeprecationEndpointTemplate, Uri.EscapeDataString(packageId)); + var request = new HttpRequestMessage(HttpMethod.Put, url); + request.Headers.TryAddWithoutValidation("X-NuGet-ApiKey", apiKey); + request.Headers.TryAddWithoutValidation( + "User-Agent", + "analyticsLibrary-release-workflow/1.0 (+https://github.com/hpractv/analyticsLibrary)"); + request.Content = new FormUrlEncodedContent(pairs); + return request; + } +} diff --git a/src/analyticsLibrary.ReleaseTooling/ReleaseConstants.cs b/src/analyticsLibrary.ReleaseTooling/ReleaseConstants.cs new file mode 100644 index 0000000..9922ef9 --- /dev/null +++ b/src/analyticsLibrary.ReleaseTooling/ReleaseConstants.cs @@ -0,0 +1,23 @@ +namespace analyticsLibrary.ReleaseTooling; + +public static class ReleaseConstants +{ + public const string LegacyDeprecationMessage = + "This package is legacy and is no longer maintained"; + + public const string PrereleaseTestingDeprecationMessage = + "This pre-release package was published for pull request validation only. Install the latest stable release instead."; + + public static readonly string[] PackageIds = + [ + "analyticsLibrary.Core", + "analyticsLibrary.Algorithms", + "analyticsLibrary.Statistics", + "analyticsLibrary.Excel", + "analyticsLibrary.Access", + "analyticsLibrary.Hadoop", + "analyticsLibrary", + ]; + + public const string PrereleaseLabelPrefix = "-beta.pr-"; +} diff --git a/src/analyticsLibrary.ReleaseTooling/VersionDeprecationPlanner.cs b/src/analyticsLibrary.ReleaseTooling/VersionDeprecationPlanner.cs new file mode 100644 index 0000000..2dd1e4e --- /dev/null +++ b/src/analyticsLibrary.ReleaseTooling/VersionDeprecationPlanner.cs @@ -0,0 +1,39 @@ +using NuGet.Versioning; + +namespace analyticsLibrary.ReleaseTooling; + +public static class VersionDeprecationPlanner +{ + public static bool IsCiBetaPrerelease(string versionString) + { + if (string.IsNullOrWhiteSpace(versionString)) + { + return false; + } + + if (!NuGetVersion.TryParse(versionString, out var v) || !v.IsPrerelease) + { + return false; + } + + return versionString.Contains(ReleaseConstants.PrereleaseLabelPrefix, StringComparison.OrdinalIgnoreCase); + } + + public static bool IsOlderStableThan(string versionString, NuGetVersion releaseVersion) + { + if (!NuGetVersion.TryParse(versionString, out var v)) + { + return false; + } + + if (v.IsPrerelease) + { + return false; + } + + return v < releaseVersion; + } + + public static NuGetVersion? TryParseReleaseVersion(string versionString) => + NuGetVersion.TryParse(versionString, out var v) ? v : null; +} diff --git a/src/analyticsLibrary.ReleaseTooling/analyticsLibrary.ReleaseTooling.csproj b/src/analyticsLibrary.ReleaseTooling/analyticsLibrary.ReleaseTooling.csproj new file mode 100644 index 0000000..251d4a1 --- /dev/null +++ b/src/analyticsLibrary.ReleaseTooling/analyticsLibrary.ReleaseTooling.csproj @@ -0,0 +1,12 @@ + + + net8.0 + enable + enable + false + analyticsLibrary.ReleaseTooling + + + + + diff --git a/tests/analyticsLibrary.ReleaseTooling.Tests/FlatContainerIndexParserTests.cs b/tests/analyticsLibrary.ReleaseTooling.Tests/FlatContainerIndexParserTests.cs new file mode 100644 index 0000000..aafac55 --- /dev/null +++ b/tests/analyticsLibrary.ReleaseTooling.Tests/FlatContainerIndexParserTests.cs @@ -0,0 +1,26 @@ +using analyticsLibrary.ReleaseTooling; + +namespace analyticsLibrary.ReleaseTooling.Tests; + +public class FlatContainerIndexParserTests +{ + [Fact] + public void ParseVersions_extracts_ordered_strings() + { + const string json = """ + {"versions":["1.0.0","2.0.0-beta.pr-1.2"]} + """; + + var v = FlatContainerIndexParser.ParseVersions(json); + + Assert.Equal(new[] { "1.0.0", "2.0.0-beta.pr-1.2" }, v); + } + + [Fact] + public void ParseVersions_missing_array_returns_empty() + { + const string json = """{"other":[]}"""; + + Assert.Empty(FlatContainerIndexParser.ParseVersions(json)); + } +} diff --git a/tests/analyticsLibrary.ReleaseTooling.Tests/GlobalUsings.cs b/tests/analyticsLibrary.ReleaseTooling.Tests/GlobalUsings.cs new file mode 100644 index 0000000..c802f44 --- /dev/null +++ b/tests/analyticsLibrary.ReleaseTooling.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; diff --git a/tests/analyticsLibrary.ReleaseTooling.Tests/NuGetFlatContainerIntegrationTests.cs b/tests/analyticsLibrary.ReleaseTooling.Tests/NuGetFlatContainerIntegrationTests.cs new file mode 100644 index 0000000..05fd6e3 --- /dev/null +++ b/tests/analyticsLibrary.ReleaseTooling.Tests/NuGetFlatContainerIntegrationTests.cs @@ -0,0 +1,34 @@ +using analyticsLibrary.ReleaseTooling; + +namespace analyticsLibrary.ReleaseTooling.Tests; + +[Trait("Category", "Integration")] +public class NuGetFlatContainerIntegrationTests +{ + [Fact] + public async Task Flat_container_index_for_core_has_versions_array() + { + using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) }; + Exception? last = null; + for (var attempt = 0; attempt < 3; attempt++) + { + try + { + var json = await http.GetStringAsync(NuGetFlatContainerHttp.GetPackageIndexUri("analyticsLibrary.Core")); + var versions = FlatContainerIndexParser.ParseVersions(json); + Assert.NotEmpty(versions); + return; + } + catch (Exception ex) + { + last = ex; + if (attempt < 2) + { + await Task.Delay(TimeSpan.FromSeconds(2)); + } + } + } + + throw last ?? new InvalidOperationException("Flat container request failed."); + } +} diff --git a/tests/analyticsLibrary.ReleaseTooling.Tests/NuGetGalleryDeprecationApiTests.cs b/tests/analyticsLibrary.ReleaseTooling.Tests/NuGetGalleryDeprecationApiTests.cs new file mode 100644 index 0000000..31e71e7 --- /dev/null +++ b/tests/analyticsLibrary.ReleaseTooling.Tests/NuGetGalleryDeprecationApiTests.cs @@ -0,0 +1,28 @@ +using System.Net.Http; +using analyticsLibrary.ReleaseTooling; + +namespace analyticsLibrary.ReleaseTooling.Tests; + +public class NuGetGalleryDeprecationApiTests +{ + [Fact] + public void CreateDeprecationRequest_uses_put_form_urlencoded() + { + using var req = NuGetGalleryDeprecationApi.CreateDeprecationRequest( + "Some.Package", + new[] { "1.0.0", "1.0.1" }, + isLegacy: true, + hasCriticalBugs: false, + isOther: false, + "msg", + "Some.Package", + "2.0.0", + "fake-key"); + + Assert.Equal(HttpMethod.Put, req.Method); + Assert.Contains("Some.Package", req.RequestUri!.AbsoluteUri, StringComparison.OrdinalIgnoreCase); + Assert.True(req.Headers.Contains("X-NuGet-ApiKey")); + Assert.NotNull(req.Content); + Assert.Equal("application/x-www-form-urlencoded", req.Content.Headers.ContentType!.MediaType); + } +} diff --git a/tests/analyticsLibrary.ReleaseTooling.Tests/VersionDeprecationPlannerTests.cs b/tests/analyticsLibrary.ReleaseTooling.Tests/VersionDeprecationPlannerTests.cs new file mode 100644 index 0000000..1e9b2e5 --- /dev/null +++ b/tests/analyticsLibrary.ReleaseTooling.Tests/VersionDeprecationPlannerTests.cs @@ -0,0 +1,33 @@ +using analyticsLibrary.ReleaseTooling; +using NuGet.Versioning; + +namespace analyticsLibrary.ReleaseTooling.Tests; + +public class VersionDeprecationPlannerTests +{ + [Theory] + [InlineData("2.0.0-beta.pr-6.2", true)] + [InlineData("2.0.0-beta", false)] + [InlineData("2.0.0", false)] + [InlineData("2.0.0-rc.1", false)] + public void IsCiBetaPrerelease_matches_pr_build_suffix(string version, bool expected) => + Assert.Equal(expected, VersionDeprecationPlanner.IsCiBetaPrerelease(version)); + + [Fact] + public void IsOlderStableThan_excludes_prerelease_and_current() + { + var rel = NuGetVersion.Parse("3.0.0"); + Assert.True(VersionDeprecationPlanner.IsOlderStableThan("2.0.0", rel)); + Assert.False(VersionDeprecationPlanner.IsOlderStableThan("3.0.0", rel)); + Assert.False(VersionDeprecationPlanner.IsOlderStableThan("3.0.1", rel)); + Assert.False(VersionDeprecationPlanner.IsOlderStableThan("3.0.0-beta.pr-1.1", rel)); + } + + [Fact] + public void Legacy_message_constant_is_exact() + { + Assert.Equal( + "This package is legacy and is no longer maintained", + ReleaseConstants.LegacyDeprecationMessage); + } +} diff --git a/tests/analyticsLibrary.ReleaseTooling.Tests/analyticsLibrary.ReleaseTooling.Tests.csproj b/tests/analyticsLibrary.ReleaseTooling.Tests/analyticsLibrary.ReleaseTooling.Tests.csproj new file mode 100644 index 0000000..7a609eb --- /dev/null +++ b/tests/analyticsLibrary.ReleaseTooling.Tests/analyticsLibrary.ReleaseTooling.Tests.csproj @@ -0,0 +1,24 @@ + + + net8.0 + false + enable + enable + analyticsLibrary.ReleaseTooling.Tests + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + diff --git a/tools/analyticsLibrary.NuGetMaintenance/Program.cs b/tools/analyticsLibrary.NuGetMaintenance/Program.cs new file mode 100644 index 0000000..d53bc34 --- /dev/null +++ b/tools/analyticsLibrary.NuGetMaintenance/Program.cs @@ -0,0 +1,160 @@ +using System.Net; +using analyticsLibrary.ReleaseTooling; +using NuGet.Versioning; + +if (args.Length < 2 || !string.Equals(args[0], "apply-deprecations", StringComparison.Ordinal)) +{ + Console.Error.WriteLine("Usage: analyticsLibrary.NuGetMaintenance apply-deprecations "); + return 1; +} + +var releaseRaw = args[1]; +var release = VersionDeprecationPlanner.TryParseReleaseVersion(releaseRaw); +if (release is null) +{ + Console.Error.WriteLine($"Invalid release version: {releaseRaw}"); + return 1; +} + +var apiKey = Environment.GetEnvironmentVariable("NUGET_API_KEY"); +if (string.IsNullOrWhiteSpace(apiKey)) +{ + Console.Error.WriteLine("NUGET_API_KEY is not set."); + return 1; +} + +using var http = new HttpClient(); +return await RunAsync(http, apiKey, release, CancellationToken.None).ConfigureAwait(false); + +static async Task RunAsync(HttpClient http, string apiKey, NuGetVersion releaseVersion, CancellationToken cancellationToken) +{ + const int chunkSize = 40; + foreach (var packageId in ReleaseConstants.PackageIds) + { + IReadOnlyList published; + try + { + published = await NuGetFlatContainerHttp.GetPublishedVersionsAsync(http, packageId, cancellationToken) + .ConfigureAwait(false); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Failed to list versions for {packageId}: {ex.Message}"); + return 1; + } + + if (published.Count == 0) + { + Console.WriteLine($"No published versions found for {packageId}; skipping."); + continue; + } + + var betas = published.Where(VersionDeprecationPlanner.IsCiBetaPrerelease).ToList(); + var legacyStables = published + .Where(v => VersionDeprecationPlanner.IsOlderStableThan(v, releaseVersion)) + .ToList(); + + foreach (var chunk in Chunk(betas, chunkSize)) + { + var code = await SendDeprecationAsync( + http, + apiKey, + packageId, + chunk, + isLegacy: false, + hasCriticalBugs: false, + isOther: true, + ReleaseConstants.PrereleaseTestingDeprecationMessage, + releaseVersion, + cancellationToken) + .ConfigureAwait(false); + if (code != 0) + { + return code; + } + } + + foreach (var chunk in Chunk(legacyStables, chunkSize)) + { + var code = await SendDeprecationAsync( + http, + apiKey, + packageId, + chunk, + isLegacy: true, + hasCriticalBugs: false, + isOther: false, + ReleaseConstants.LegacyDeprecationMessage, + releaseVersion, + cancellationToken) + .ConfigureAwait(false); + if (code != 0) + { + return code; + } + } + } + + return 0; +} + +static List> Chunk(IReadOnlyList items, int size) +{ + var chunks = new List>(); + for (var i = 0; i < items.Count; i += size) + { + chunks.Add(items.Skip(i).Take(size).ToList()); + } + + return chunks; +} + +static async Task SendDeprecationAsync( + HttpClient http, + string apiKey, + string packageId, + IReadOnlyList versions, + bool isLegacy, + bool hasCriticalBugs, + bool isOther, + string message, + NuGetVersion releaseVersion, + CancellationToken cancellationToken) +{ + if (versions.Count == 0) + { + return 0; + } + + var normalizedRelease = releaseVersion.ToNormalizedString(); + using var request = NuGetGalleryDeprecationApi.CreateDeprecationRequest( + packageId, + versions, + isLegacy, + hasCriticalBugs, + isOther, + message, + packageId, + normalizedRelease, + apiKey); + + using var response = await http.SendAsync(request, cancellationToken).ConfigureAwait(false); + if (response.IsSuccessStatusCode) + { + Console.WriteLine( + $"Deprecated {versions.Count} version(s) of {packageId} (legacy={isLegacy}, other={isOther})."); + return 0; + } + + var body = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + Console.Error.WriteLine( + $"NuGet deprecation failed for {packageId}: {(int)response.StatusCode} {response.ReasonPhrase}. Body: {body}"); + + if (response.StatusCode == HttpStatusCode.Forbidden) + { + Console.Error.WriteLine( + "Hint: ensure the API key allows package push/unlist and that the NuGet.org account has the manage-deprecation API enabled for this key."); + } + + return 1; +} diff --git a/tools/analyticsLibrary.NuGetMaintenance/analyticsLibrary.NuGetMaintenance.csproj b/tools/analyticsLibrary.NuGetMaintenance/analyticsLibrary.NuGetMaintenance.csproj new file mode 100644 index 0000000..4567cdb --- /dev/null +++ b/tools/analyticsLibrary.NuGetMaintenance/analyticsLibrary.NuGetMaintenance.csproj @@ -0,0 +1,13 @@ + + + Exe + net8.0 + enable + enable + false + analyticsLibrary.NuGetMaintenance + + + + + From 54239c015479b61fcbd866f57c0c4f6ff9f85bd7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 17:00:58 +0000 Subject: [PATCH 2/6] fix: stabilize release tooling tests and optimize version chunking Agent-Logs-Url: https://github.com/hpractv/analyticsLibrary/sessions/5b1b9a5b-7038-4a2f-9e50-5dc4d00f3803 Co-authored-by: hpractv <13870231+hpractv@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- .../NuGetFlatContainerIntegrationTests.cs | 35 ++++++++----------- .../Program.cs | 9 ++++- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc50e73..4d0ef32 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -140,7 +140,7 @@ jobs: run: dotnet build -c Release --no-restore - name: Test - run: dotnet test -c Release --no-build --verbosity normal + run: dotnet test -c Release --no-build --verbosity normal --filter "Category!=Integration" - name: Pack run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7f5203b..2f22686 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,7 @@ jobs: run: dotnet build -c Release --no-restore - name: Test - run: dotnet test -c Release --no-build --verbosity normal + run: dotnet test -c Release --no-build --verbosity normal --filter "Category!=Integration" - name: Pack all packages run: | diff --git a/tests/analyticsLibrary.ReleaseTooling.Tests/NuGetFlatContainerIntegrationTests.cs b/tests/analyticsLibrary.ReleaseTooling.Tests/NuGetFlatContainerIntegrationTests.cs index 05fd6e3..44696fe 100644 --- a/tests/analyticsLibrary.ReleaseTooling.Tests/NuGetFlatContainerIntegrationTests.cs +++ b/tests/analyticsLibrary.ReleaseTooling.Tests/NuGetFlatContainerIntegrationTests.cs @@ -1,34 +1,27 @@ +using System.Net; +using System.Net.Http; using analyticsLibrary.ReleaseTooling; namespace analyticsLibrary.ReleaseTooling.Tests; -[Trait("Category", "Integration")] public class NuGetFlatContainerIntegrationTests { [Fact] public async Task Flat_container_index_for_core_has_versions_array() { - using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) }; - Exception? last = null; - for (var attempt = 0; attempt < 3; attempt++) - { - try + var handler = new StubHttpMessageHandler(_ => + new HttpResponseMessage(HttpStatusCode.OK) { - var json = await http.GetStringAsync(NuGetFlatContainerHttp.GetPackageIndexUri("analyticsLibrary.Core")); - var versions = FlatContainerIndexParser.ParseVersions(json); - Assert.NotEmpty(versions); - return; - } - catch (Exception ex) - { - last = ex; - if (attempt < 2) - { - await Task.Delay(TimeSpan.FromSeconds(2)); - } - } - } + Content = new StringContent("""{"versions":["1.0.0","2.0.0"]}""") + }); + using var http = new HttpClient(handler); + var versions = await NuGetFlatContainerHttp.GetPublishedVersionsAsync(http, "analyticsLibrary.Core"); + Assert.NotEmpty(versions); + } - throw last ?? new InvalidOperationException("Flat container request failed."); + private sealed class StubHttpMessageHandler(Func handler) : HttpMessageHandler + { + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => + Task.FromResult(handler(request)); } } diff --git a/tools/analyticsLibrary.NuGetMaintenance/Program.cs b/tools/analyticsLibrary.NuGetMaintenance/Program.cs index d53bc34..d270a29 100644 --- a/tools/analyticsLibrary.NuGetMaintenance/Program.cs +++ b/tools/analyticsLibrary.NuGetMaintenance/Program.cs @@ -103,7 +103,14 @@ static List> Chunk(IReadOnlyList items, int size) var chunks = new List>(); for (var i = 0; i < items.Count; i += size) { - chunks.Add(items.Skip(i).Take(size).ToList()); + var end = Math.Min(i + size, items.Count); + var chunk = new List(end - i); + for (var j = i; j < end; j++) + { + chunk.Add(items[j]); + } + + chunks.Add(chunk); } return chunks; From 9a2baf0b2cdccfd0fd60002ed7a9a18bbfb8df21 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 17:04:31 +0000 Subject: [PATCH 3/6] test: rename NuGet flat container unit test Agent-Logs-Url: https://github.com/hpractv/analyticsLibrary/sessions/5b1b9a5b-7038-4a2f-9e50-5dc4d00f3803 Co-authored-by: hpractv <13870231+hpractv@users.noreply.github.com> --- ...tContainerIntegrationTests.cs => NuGetFlatContainerTests.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/analyticsLibrary.ReleaseTooling.Tests/{NuGetFlatContainerIntegrationTests.cs => NuGetFlatContainerTests.cs} (95%) diff --git a/tests/analyticsLibrary.ReleaseTooling.Tests/NuGetFlatContainerIntegrationTests.cs b/tests/analyticsLibrary.ReleaseTooling.Tests/NuGetFlatContainerTests.cs similarity index 95% rename from tests/analyticsLibrary.ReleaseTooling.Tests/NuGetFlatContainerIntegrationTests.cs rename to tests/analyticsLibrary.ReleaseTooling.Tests/NuGetFlatContainerTests.cs index 44696fe..c59d322 100644 --- a/tests/analyticsLibrary.ReleaseTooling.Tests/NuGetFlatContainerIntegrationTests.cs +++ b/tests/analyticsLibrary.ReleaseTooling.Tests/NuGetFlatContainerTests.cs @@ -4,7 +4,7 @@ namespace analyticsLibrary.ReleaseTooling.Tests; -public class NuGetFlatContainerIntegrationTests +public class NuGetFlatContainerTests { [Fact] public async Task Flat_container_index_for_core_has_versions_array() From ac3a2b8ba39501d75a349376d25e0c373ab95b77 Mon Sep 17 00:00:00 2001 From: hpractv Date: Mon, 11 May 2026 14:36:51 -0600 Subject: [PATCH 4/6] chore: update GitHub Actions workflows and refactor class names for consistency - Upgraded actions/checkout, actions/setup-dotnet, and actions/upload-artifact to their latest versions in CI and release workflows. - Refactored class names in the analytics library to follow PascalCase convention, improving code readability and consistency across the project. --- .github/workflows/ci.yml | 10 +- .github/workflows/release.yml | 6 +- src/analyticsLibrary.Access/access.cs | 24 +- src/analyticsLibrary.Algorithms/searching.cs | 2 +- src/analyticsLibrary.Algorithms/sorting.cs | 2 +- src/analyticsLibrary.Core/analytics.cs | 2 +- src/analyticsLibrary.Core/column.cs | 2 +- src/analyticsLibrary.Core/csv.cs | 22 +- src/analyticsLibrary.Core/csvStream.cs | 240 +++++++++--------- src/analyticsLibrary.Core/csv_base.cs | 8 +- src/analyticsLibrary.Core/data.cs | 4 +- src/analyticsLibrary.Core/extensions.cs | 2 +- src/analyticsLibrary.Core/fixedWidthFile.cs | 8 +- .../fixedWidthFileStream.cs | 18 +- src/analyticsLibrary.Core/login.cs | 2 +- src/analyticsLibrary.Core/presentation.cs | 2 +- src/analyticsLibrary.Core/table.cs | 4 +- src/analyticsLibrary.Core/utility.cs | 2 +- src/analyticsLibrary.Excel/extensions.cs | 19 +- src/analyticsLibrary.Hadoop/hadoop.cs | 8 +- src/analyticsLibrary.Statistics/extensions.cs | 2 +- .../AlgorithmsTests.cs | 6 +- .../analyticsLibrary.Core.Tests/CoreTests.cs | 2 +- .../ExcelTests.cs | 2 +- 24 files changed, 200 insertions(+), 199 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d0ef32..b5dec78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: actionlint uses: rhysd/actionlint@v1.7.12 @@ -30,7 +30,7 @@ jobs: any_changed: ${{ steps.flags.outputs.any_changed }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -126,10 +126,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: '8.0.x' @@ -228,7 +228,7 @@ jobs: echo "All expected packages found." - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: nuget-packages-${{ github.run_id }} path: ./artifacts/*.nupkg diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f22686..27c54e0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,10 +19,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: '8.0.x' @@ -75,7 +75,7 @@ jobs: echo "All ${{ steps.version.outputs.VERSION }} packages found and ready for deployment." - name: Upload release artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: nuget-packages-${{ steps.version.outputs.VERSION }} path: ./artifacts/*.nupkg diff --git a/src/analyticsLibrary.Access/access.cs b/src/analyticsLibrary.Access/access.cs index 4fefd5f..026fc0a 100644 --- a/src/analyticsLibrary.Access/access.cs +++ b/src/analyticsLibrary.Access/access.cs @@ -6,13 +6,13 @@ namespace analyticsLibrary.Access { - public class access + public class Access { - public string filePath { get; private set; } + public string filePath { get; private set; } - public access(string filePath) => this.filePath = filePath; + public Access(string filePath) => this.filePath = filePath; - private OleDbConnection _connection; + private OleDbConnection _connection; private OleDbConnection connection { @@ -37,7 +37,7 @@ public IEnumerable execute(string sql) return results.Rows.Cast(); } - public IEnumerable tablesByName(string tableName = null) + public IEnumerable
tablesByName(string tableName = null) { var results = (IEnumerable)null; @@ -54,7 +54,7 @@ public IEnumerable
tablesByName(string tableName = null) } return results? - .Select(r => new table() + .Select(r => new Table() { schema = r.Field("TABLE_SCHEMA"), name = r.Field("TABLE_NAME"), @@ -62,7 +62,7 @@ public IEnumerable
tablesByName(string tableName = null) .Where(r => tableName == null || (tableName != null && r.name.ToLower().Contains(tableName.ToLower()))); } - private IEnumerable columns() + private IEnumerable columns() { var results = (IEnumerable)null; @@ -79,7 +79,7 @@ private IEnumerable columns() } return results? - .Select(c => new column() + .Select(c => new Column() { parentTable = c["table_name"].ToString(), name = c["column_name"].ToString(), @@ -87,15 +87,15 @@ private IEnumerable columns() length = c.IsNull("character_maximum_length") ? (int?)null : int.Parse(c["character_maximum_length"].ToString()), nullable = c["is_nullable"].ToString().ToUpper() == "TRUE" ? true : false, }); - } + } - public IEnumerable columnsByTable(string tableName = null) + public IEnumerable columnsByTable(string tableName = null) { return columns() ?.Where(r => tableName == null || (tableName != null && r.parentTable.ToLower().Contains(tableName.ToLower()))); - } + } - public IEnumerable columnsByName(string columnName = null) + public IEnumerable columnsByName(string columnName = null) { return columns() ?.Where(r => columnName == null || (columnName != null && r.name.ToLower().Contains(columnName.ToLower()))); diff --git a/src/analyticsLibrary.Algorithms/searching.cs b/src/analyticsLibrary.Algorithms/searching.cs index eda0d01..dc3e2a7 100644 --- a/src/analyticsLibrary.Algorithms/searching.cs +++ b/src/analyticsLibrary.Algorithms/searching.cs @@ -6,7 +6,7 @@ namespace analyticsLibrary.Algorithms { - public static class searching + public static class Searching { private static bool stringSearch(object value1, object value2) => string.Compare(value1.ToString(), value2.ToString()) <= 0; diff --git a/src/analyticsLibrary.Algorithms/sorting.cs b/src/analyticsLibrary.Algorithms/sorting.cs index 63d7c06..1338428 100644 --- a/src/analyticsLibrary.Algorithms/sorting.cs +++ b/src/analyticsLibrary.Algorithms/sorting.cs @@ -4,7 +4,7 @@ namespace analyticsLibrary.Algorithms { - public static class sorting + public static class Sorting { private static bool stringLess(object value1, object value2) => string.Compare(value1.ToString(), value2.ToString()) <= 0; diff --git a/src/analyticsLibrary.Core/analytics.cs b/src/analyticsLibrary.Core/analytics.cs index 3df329b..91f5da1 100644 --- a/src/analyticsLibrary.Core/analytics.cs +++ b/src/analyticsLibrary.Core/analytics.cs @@ -4,7 +4,7 @@ namespace analyticsLibrary.Core { - public static class analytics + public static class Analytics { public static string compress(this string value, string compress) => Regex.Replace(value, string.Format("([{0}]+)", compress), compress); diff --git a/src/analyticsLibrary.Core/column.cs b/src/analyticsLibrary.Core/column.cs index 97ee1e0..527192d 100644 --- a/src/analyticsLibrary.Core/column.cs +++ b/src/analyticsLibrary.Core/column.cs @@ -3,7 +3,7 @@ namespace analyticsLibrary.Core { [Serializable] - public class column + public class Column { public string parentTable { get; set; } public string name { get; set; } diff --git a/src/analyticsLibrary.Core/csv.cs b/src/analyticsLibrary.Core/csv.cs index 4d13ed3..af26f41 100644 --- a/src/analyticsLibrary.Core/csv.cs +++ b/src/analyticsLibrary.Core/csv.cs @@ -5,7 +5,7 @@ namespace analyticsLibrary.Core { - public class csv : IKeyIndex + public class Csv : IKeyIndex { private string _file { get; set; } private char _delimeter = ','; @@ -40,19 +40,19 @@ public int[] fieldMaxLengths private bool _hasHeader; - public csv(string file) : + public Csv(string file) : this(file, ',', true) { } - public csv(string file, char delimiter) : + public Csv(string file, char delimiter) : this(file, delimiter, true) { } - public csv(string file, bool hasHeader = true) : + public Csv(string file, bool hasHeader = true) : this(file, ',', hasHeader) { } - public csv(string file, char delimeter, bool hasHeader = true) + public Csv(string file, char delimeter, bool hasHeader = true) { this._delimeter = delimeter; this._file = file; @@ -84,8 +84,8 @@ private void buildHeader() data.Length < 100 ? "00" : data.Length < 1000 ? - "000" : - //else + "000" : + //else "0"; _header = data @@ -126,22 +126,22 @@ public bool keyExists(string key, out int index) return false; } - private List> _data; + private List> _data; - public IEnumerable> data + public IEnumerable> data { get { if (_header == null) buildHeader(); if (_data == null) { - _data = new List>(); + _data = new List>(); var stream = new StreamReader(this._file); var addHeader = _hasHeader; while (stream.Peek() > -1) { - var record = new data(this, stream.ReadLine().fromCsv(_delimeter)); + var record = new Data(this, stream.ReadLine().fromCsv(_delimeter)); if (addHeader) { diff --git a/src/analyticsLibrary.Core/csvStream.cs b/src/analyticsLibrary.Core/csvStream.cs index 7cd6107..f50315e 100644 --- a/src/analyticsLibrary.Core/csvStream.cs +++ b/src/analyticsLibrary.Core/csvStream.cs @@ -1,130 +1,130 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.IO; -using System.Linq; - -namespace analyticsLibrary.Core -{ - public class csvStream : IKeyIndex, IDisposable - { - private string _file { get; set; } - private char _delimeter = ','; - - private bool _hasHeader; - - private StreamReader _stream; - - public csvStream(string file) : - this(file, ',', true) - { } - - public csvStream(string file, char delimiter) : - this(file, delimiter, true) - { } - - public csvStream(string file, bool hasHeader = true) : - this(file, ',', hasHeader) - { } - - public csvStream(string file, char delimeter, bool hasHeader = true) - { - this._delimeter = delimeter; - this._file = file; - this._hasHeader = hasHeader; - this._stream = new StreamReader(this._file); - } - - private indexObject[] _header_lower_case; - private indexObject[] _header; - - private void buildHeader() - { - if (_header == null) - { - var headerStream = new StreamReader(this._file); - var firstRow = headerStream.ReadLine().fromCsv(_delimeter); - if (_hasHeader) - { - _header = firstRow.index(); - for (int i = 0; i < _header.Length; i++) - _header[i].value = _header[i].value ?? $"Column{_header[i].index + 1}"; - } - else - { - var record = firstRow - .index(); - - var format = record.Length < 10 ? - "0" : - record.Length < 100 ? - "00" : - record.Length < 1000 ? +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; + +namespace analyticsLibrary.Core +{ + public class csvStream : IKeyIndex, IDisposable + { + private string _file { get; set; } + private char _delimeter = ','; + + private bool _hasHeader; + + private StreamReader _stream; + + public csvStream(string file) : + this(file, ',', true) + { } + + public csvStream(string file, char delimiter) : + this(file, delimiter, true) + { } + + public csvStream(string file, bool hasHeader = true) : + this(file, ',', hasHeader) + { } + + public csvStream(string file, char delimeter, bool hasHeader = true) + { + this._delimeter = delimeter; + this._file = file; + this._hasHeader = hasHeader; + this._stream = new StreamReader(this._file); + } + + private indexObject[] _header_lower_case; + private indexObject[] _header; + + private void buildHeader() + { + if (_header == null) + { + var headerStream = new StreamReader(this._file); + var firstRow = headerStream.ReadLine().fromCsv(_delimeter); + if (_hasHeader) + { + _header = firstRow.index(); + for (int i = 0; i < _header.Length; i++) + _header[i].value = _header[i].value ?? $"Column{_header[i].index + 1}"; + } + else + { + var record = firstRow + .index(); + + var format = record.Length < 10 ? + "0" : + record.Length < 100 ? + "00" : + record.Length < 1000 ? "000" : //else - "0"; - - _header = record - .Select(r => $"Column{(r.index + 1).ToString(format)}") - .index(); - } - headerStream.Close(); - _header_lower_case = _header.Select(h => h.value.ToLower()).index(); - } - } - - public IEnumerable header - { - get - { - buildHeader(); - return _header.Select(h => h.value); - } + "0"; + + _header = record + .Select(r => $"Column{(r.index + 1).ToString(format)}") + .index(); + } + headerStream.Close(); + _header_lower_case = _header.Select(h => h.value.ToLower()).index(); + } + } + + public IEnumerable header + { + get + { + buildHeader(); + return _header.Select(h => h.value); + } + } + + public bool keyExists(string key) => keyExists(key, out var index); + + public bool keyExists(string key, out int index) + { + buildHeader(); + var field = _header_lower_case.SingleOrDefault(v => v.value == key.ToLower()); + if (field != null) + { + index = field.index; + return true; + } + index = -1; + return false; + } + + public bool endOfFile + { + get => _stream.EndOfStream; } - public bool keyExists(string key) => keyExists(key, out var index); - - public bool keyExists(string key, out int index) - { - buildHeader(); - var field = _header_lower_case.SingleOrDefault(v => v.value == key.ToLower()); - if (field != null) - { - index = field.index; - return true; - } - index = -1; - return false; - } - - public bool endOfFile - { - get => _stream.EndOfStream; - } - private int _recordCount = 1; - + public int recordCount { - get => _recordCount; - private set => _recordCount = value; - } - - public data nextRecord - { - get - { - if (_header == null) buildHeader(); - if (!endOfFile) - { - var data = new data(this, _stream.ReadLine().fromCsv(this._delimeter)); + get => _recordCount; + private set => _recordCount = value; + } + + public Data nextRecord + { + get + { + if (_header == null) buildHeader(); + if (!endOfFile) + { + var data = new Data(this, _stream.ReadLine().fromCsv(this._delimeter)); if (recordCount++ == 1 && _hasHeader) - data = new data(this, _stream.ReadLine().fromCsv(this._delimeter)); - + data = new Data(this, _stream.ReadLine().fromCsv(this._delimeter)); + return data; - } - - return default(data); + } + + return default(Data); } } @@ -139,6 +139,6 @@ public void Dispose() { this._stream.Close(); this._stream.Dispose(); - } - } + } + } } \ No newline at end of file diff --git a/src/analyticsLibrary.Core/csv_base.cs b/src/analyticsLibrary.Core/csv_base.cs index b777695..1a8afaa 100644 --- a/src/analyticsLibrary.Core/csv_base.cs +++ b/src/analyticsLibrary.Core/csv_base.cs @@ -125,22 +125,22 @@ public bool keyExists(string key, out int index) return false; } - protected List> _records; + protected List> _records; - public IEnumerable> records + public IEnumerable> records { get { if (_header == null) buildHeader(); if (_records == null) { - _records = new List>(); + _records = new List>(); var stream = new StreamReader(this._file); var addHeader = _hasHeader; while (stream.Peek() > -1) { - var record = new data(this, stream.ReadLine().fromCsv(_delimeter)); + var record = new Data(this, stream.ReadLine().fromCsv(_delimeter)); if (addHeader) { diff --git a/src/analyticsLibrary.Core/data.cs b/src/analyticsLibrary.Core/data.cs index f23709c..a27938e 100644 --- a/src/analyticsLibrary.Core/data.cs +++ b/src/analyticsLibrary.Core/data.cs @@ -3,11 +3,11 @@ namespace analyticsLibrary.Core { - public class data + public class Data { internal IKeyIndex parent { get; private set; } - public data(IKeyIndex parent, TValue[] values) + public Data(IKeyIndex parent, TValue[] values) { this.parent = parent; this.values = values; diff --git a/src/analyticsLibrary.Core/extensions.cs b/src/analyticsLibrary.Core/extensions.cs index 4251af2..055d71b 100644 --- a/src/analyticsLibrary.Core/extensions.cs +++ b/src/analyticsLibrary.Core/extensions.cs @@ -10,7 +10,7 @@ namespace analyticsLibrary.Core { - public static class extensions + public static class Extensions { public static string stringOrNull(this object value, int length) { diff --git a/src/analyticsLibrary.Core/fixedWidthFile.cs b/src/analyticsLibrary.Core/fixedWidthFile.cs index 5f950cf..ccbdda0 100644 --- a/src/analyticsLibrary.Core/fixedWidthFile.cs +++ b/src/analyticsLibrary.Core/fixedWidthFile.cs @@ -95,22 +95,22 @@ public bool keyExists(string key, out int index) return false; } - protected List> _records; + protected List> _records; - public IEnumerable> records + public IEnumerable> records { get { if (_header == null) buildHeader(); if (_records == null) { - _records = new List>(); + _records = new List>(); var stream = new StreamReader(this._file); var addHeader = _hasHeader; while (stream.Peek() > -1) { - var record = new data(this, stream.ReadLine().splitFromPositions(_positions)); + var record = new Data(this, stream.ReadLine().splitFromPositions(_positions)); if (addHeader) { diff --git a/src/analyticsLibrary.Core/fixedWidthFileStream.cs b/src/analyticsLibrary.Core/fixedWidthFileStream.cs index fd955dc..da62627 100644 --- a/src/analyticsLibrary.Core/fixedWidthFileStream.cs +++ b/src/analyticsLibrary.Core/fixedWidthFileStream.cs @@ -29,7 +29,7 @@ public fixedWidthFileStream(string file, int[] positions, bool hasHeader = true) this._positions = positions; this._file = file; this._hasHeader = hasHeader; - this._stream = new StreamReader(this._file); + this._stream = new StreamReader(this._file); } protected indexObject[] _header_lower_case; @@ -57,8 +57,8 @@ protected void buildHeader() record.Length < 100 ? "00" : record.Length < 1000 ? - "000" : - //else + "000" : + //else "0"; _header = record @@ -99,24 +99,24 @@ public bool endOfFile get => _stream.EndOfStream; } - public int recordCount { get; private set; } + public int recordCount { get; private set; } - public data nextRecord + public Data nextRecord { get { if (_header == null) buildHeader(); if (!endOfFile) - { - var record = new data(this, _stream.ReadLine().splitFromPositions(_positions)); + { + var record = new Data(this, _stream.ReadLine().splitFromPositions(_positions)); if (recordCount++ == 0 && _hasHeader) - record = new data(this, _stream.ReadLine().splitFromPositions(_positions)); + record = new Data(this, _stream.ReadLine().splitFromPositions(_positions)); return record; } - return default(data); + return default(Data); } } diff --git a/src/analyticsLibrary.Core/login.cs b/src/analyticsLibrary.Core/login.cs index 8b81048..0bab2e3 100644 --- a/src/analyticsLibrary.Core/login.cs +++ b/src/analyticsLibrary.Core/login.cs @@ -1,6 +1,6 @@ namespace analyticsLibrary.Core { - public class login + public class Login { public string dsn { get; set; } public int port { get; set; } diff --git a/src/analyticsLibrary.Core/presentation.cs b/src/analyticsLibrary.Core/presentation.cs index d47bc01..e00d90d 100644 --- a/src/analyticsLibrary.Core/presentation.cs +++ b/src/analyticsLibrary.Core/presentation.cs @@ -7,7 +7,7 @@ namespace analyticsLibrary.Core { - public static class presentation + public static class Presentation { private const string hexColor = @"^#(((?[a-fA-F\d]{2}?)(?[a-fA-F\d]{2}?)(?[a-fA-F\d]{2}?))|((?[a-fA-F\d]{1}?)(?[a-fA-F\d]{1}?)(?[a-fA-F\d]{1}?)))$"; private static Regex colorCheck = new Regex(hexColor, RegexOptions.Singleline | RegexOptions.Compiled); diff --git a/src/analyticsLibrary.Core/table.cs b/src/analyticsLibrary.Core/table.cs index aa9f3ec..fcc3666 100644 --- a/src/analyticsLibrary.Core/table.cs +++ b/src/analyticsLibrary.Core/table.cs @@ -4,11 +4,11 @@ namespace analyticsLibrary.Core { [Serializable] - public class table + public class Table { public string schema { get; set; } public string name { get; set; } - public IEnumerable columns { get; set; } + public IEnumerable columns { get; set; } } } diff --git a/src/analyticsLibrary.Core/utility.cs b/src/analyticsLibrary.Core/utility.cs index 6f4607c..693971c 100644 --- a/src/analyticsLibrary.Core/utility.cs +++ b/src/analyticsLibrary.Core/utility.cs @@ -4,7 +4,7 @@ namespace analyticsLibrary.Core { - internal static class utility + internal static class Utility { internal static string assemblyDirectory { diff --git a/src/analyticsLibrary.Excel/extensions.cs b/src/analyticsLibrary.Excel/extensions.cs index c5e38f6..143cb05 100644 --- a/src/analyticsLibrary.Excel/extensions.cs +++ b/src/analyticsLibrary.Excel/extensions.cs @@ -4,7 +4,7 @@ using analyticsLibrary.Core; namespace analyticsLibrary.Excel { - public static class extensions + public static class Extensions { public static int columnNumber(this columnLettersEnum value) { @@ -44,14 +44,14 @@ public static void numberFormatColumns(this object sheet, int[] columns, string } } - public static object rowValue(this DataRow row, string field) + public static object rowValue(this DataRow row, string field) { - return row.rowValue(field); + return row.rowValue(field); } - public static valueType rowValue(this DataRow row, string field) + public static TField rowValue(this DataRow row, string field) { var fieldName = string.Empty; - var attributes = typeof(type).GetMember(field)[0] + var attributes = typeof(TMember).GetMember(field)[0] .GetCustomAttributes(true); var attribute = attributes.FirstOrDefault(f => f is sheetColumnAttribute) as sheetColumnAttribute; @@ -66,11 +66,12 @@ public static valueType rowValue(this DataRow row, string field } } } - if (string.IsNullOrWhiteSpace(fieldName)) throw new columnNotFoundException(); - if (!row.containsColumn(fieldName)) throw new columnNotFoundException(); + if (string.IsNullOrWhiteSpace(fieldName)) throw new ColumnNotFoundException(); + if (!row.containsColumn(fieldName)) throw new ColumnNotFoundException(); - return (valueType)(row.IsNull(fieldName) ? null : row.Field(fieldName)); + return (TField)(row.IsNull(fieldName) ? null : row.Field(fieldName)); } - public class columnNotFoundException : ApplicationException { } } + + public class ColumnNotFoundException : ApplicationException { } } diff --git a/src/analyticsLibrary.Hadoop/hadoop.cs b/src/analyticsLibrary.Hadoop/hadoop.cs index 446199b..7194689 100644 --- a/src/analyticsLibrary.Hadoop/hadoop.cs +++ b/src/analyticsLibrary.Hadoop/hadoop.cs @@ -70,10 +70,10 @@ public IEnumerable execute(string sql) return results.Rows.Cast(); } - public IEnumerable
tablesByName(string tableName = null) + public IEnumerable
tablesByName(string tableName = null) { return this.execute("SHOW TABLES") - .Select(r => new table() + .Select(r => new Table() { schema = this.schema, name = r.containsColumn("tab_name") ? r.Field("tab_name") : r.Field("name"), @@ -82,7 +82,7 @@ public IEnumerable
tablesByName(string tableName = null) string.IsNullOrWhiteSpace(tableName) || (!string.IsNullOrWhiteSpace(tableName) && t.name.ToLower().Contains(tableName))); } - public IEnumerable columnsByTable(string tableName) + public IEnumerable columnsByTable(string tableName) { var typeRegEx = @"(?[A-z\d_]+>?)(\((?[\d]+?)\))*"; return execute($"describe {tableName}") @@ -91,7 +91,7 @@ public IEnumerable columnsByTable(string tableName) var matches = Regex.Match( c.containsColumn("data_type") ? c.Field("data_type") : c.Field("type"), typeRegEx, RegexOptions.IgnoreCase); - return new column() + return new Column() { parentTable = tableName, name = c.containsColumn("col_name") ? c.Field("col_name") : c.Field("name"), diff --git a/src/analyticsLibrary.Statistics/extensions.cs b/src/analyticsLibrary.Statistics/extensions.cs index aefabfe..6ef8aca 100644 --- a/src/analyticsLibrary.Statistics/extensions.cs +++ b/src/analyticsLibrary.Statistics/extensions.cs @@ -5,7 +5,7 @@ namespace analyticsLibrary.Statistics { - public static class extensions + public static class Extensions { public static double standardDeviation(this int[] values) { diff --git a/tests/analyticsLibrary.Algorithms.Tests/AlgorithmsTests.cs b/tests/analyticsLibrary.Algorithms.Tests/AlgorithmsTests.cs index cbc10a1..accd5e4 100644 --- a/tests/analyticsLibrary.Algorithms.Tests/AlgorithmsTests.cs +++ b/tests/analyticsLibrary.Algorithms.Tests/AlgorithmsTests.cs @@ -86,7 +86,7 @@ public class SortingPickFunctionTests [Fact] public void PickFunction_Int_ReturnsLessOrEqualForAscending() { - var fn = sorting.pickFunction(descending: false); + var fn = Sorting.pickFunction(descending: false); Assert.True(fn(1, 2)); Assert.True(fn(2, 2)); Assert.False(fn(3, 2)); @@ -95,7 +95,7 @@ public void PickFunction_Int_ReturnsLessOrEqualForAscending() [Fact] public void PickFunction_Double_ReturnsExpectedComparison() { - var fn = sorting.pickFunction(descending: false); + var fn = Sorting.pickFunction(descending: false); Assert.True(fn(1.5, 2.5)); Assert.False(fn(2.5, 1.5)); } @@ -103,7 +103,7 @@ public void PickFunction_Double_ReturnsExpectedComparison() [Fact] public void PickFunction_String_ComparesLexicographically() { - var fn = sorting.pickFunction(descending: false); + var fn = Sorting.pickFunction(descending: false); Assert.True(fn("apple", "banana")); Assert.False(fn("zebra", "ant")); } diff --git a/tests/analyticsLibrary.Core.Tests/CoreTests.cs b/tests/analyticsLibrary.Core.Tests/CoreTests.cs index 83d4311..29f679c 100644 --- a/tests/analyticsLibrary.Core.Tests/CoreTests.cs +++ b/tests/analyticsLibrary.Core.Tests/CoreTests.cs @@ -111,7 +111,7 @@ public void WriteCsv_ThenRead_PreservesData() rows.writeCsv(tempFile, header, ','); - var csvReader = new csv(tempFile); + var csvReader = new Csv(tempFile); var data = csvReader.data.ToList(); Assert.Equal(2, data.Count); diff --git a/tests/analyticsLibrary.Excel.Tests/ExcelTests.cs b/tests/analyticsLibrary.Excel.Tests/ExcelTests.cs index 3f271b0..e09c37c 100644 --- a/tests/analyticsLibrary.Excel.Tests/ExcelTests.cs +++ b/tests/analyticsLibrary.Excel.Tests/ExcelTests.cs @@ -64,7 +64,7 @@ public void RowValue_Returns_Value_And_Throws_When_Missing() row2["Other"] = "x"; dt2.Rows.Add(row2); - Assert.Throws(() => dt2.Rows[0].rowValue("ColA")); + Assert.Throws(() => dt2.Rows[0].rowValue("ColA")); } [Fact] From bb03b46c3732fffff2d6dcd82fd2df7dca0d054d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 19:13:03 +0000 Subject: [PATCH 5/6] fix: restore public APIs and harden release workflow validation Agent-Logs-Url: https://github.com/hpractv/analyticsLibrary/sessions/a451a23a-a4a9-47a7-9dd4-fa7930371322 Co-authored-by: hpractv <13870231+hpractv@users.noreply.github.com> --- .github/workflows/release.yml | 48 +++- src/analyticsLibrary.Access/access.cs | 24 +- src/analyticsLibrary.Algorithms/searching.cs | 2 +- src/analyticsLibrary.Algorithms/sorting.cs | 2 +- src/analyticsLibrary.Core/analytics.cs | 2 +- src/analyticsLibrary.Core/column.cs | 2 +- src/analyticsLibrary.Core/csv.cs | 22 +- src/analyticsLibrary.Core/csvStream.cs | 240 +++++++++--------- src/analyticsLibrary.Core/csv_base.cs | 8 +- src/analyticsLibrary.Core/data.cs | 4 +- src/analyticsLibrary.Core/extensions.cs | 2 +- src/analyticsLibrary.Core/fixedWidthFile.cs | 8 +- .../fixedWidthFileStream.cs | 18 +- src/analyticsLibrary.Core/login.cs | 2 +- src/analyticsLibrary.Core/presentation.cs | 2 +- src/analyticsLibrary.Core/table.cs | 4 +- src/analyticsLibrary.Core/utility.cs | 2 +- src/analyticsLibrary.Excel/extensions.cs | 19 +- src/analyticsLibrary.Hadoop/hadoop.cs | 8 +- src/analyticsLibrary.Statistics/extensions.cs | 2 +- .../AlgorithmsTests.cs | 6 +- .../analyticsLibrary.Core.Tests/CoreTests.cs | 2 +- .../ExcelTests.cs | 2 +- 23 files changed, 231 insertions(+), 200 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 27c54e0..4aefa58 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,17 +46,49 @@ jobs: - name: Test run: dotnet test -c Release --no-build --verbosity normal --filter "Category!=Integration" + - name: Validate release version against project versions + run: | + V="${{ steps.version.outputs.VERSION }}" + if ! [[ "$V" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "ERROR: Release version must be SemVer major.minor.patch (got '$V')." + exit 1 + fi + + PROJECTS=( + src/analyticsLibrary.Core/analyticsLibrary.Core.csproj + src/analyticsLibrary.Algorithms/analyticsLibrary.Algorithms.csproj + src/analyticsLibrary.Statistics/analyticsLibrary.Statistics.csproj + src/analyticsLibrary.Excel/analyticsLibrary.Excel.csproj + src/analyticsLibrary.Access/analyticsLibrary.Access.csproj + src/analyticsLibrary.Hadoop/analyticsLibrary.Hadoop.csproj + src/analyticsLibrary/analyticsLibrary.csproj + ) + + for csproj in "${PROJECTS[@]}"; do + current=$(grep -oPm1 '\K[^<]+' "$csproj") + if [ -z "$current" ]; then + echo "ERROR: Could not read from $csproj" + exit 1 + fi + + lower=$(printf '%s\n%s\n' "$V" "$current" | sort -V | head -n1) + if [ "$lower" != "$current" ] && [ "$V" != "$current" ]; then + echo "ERROR: Requested release version $V is lower than project version $current in $csproj" + exit 1 + fi + done + - name: Pack all packages run: | mkdir -p ./artifacts V="${{ steps.version.outputs.VERSION }}" - dotnet pack src/analyticsLibrary.Core/analyticsLibrary.Core.csproj -c Release --no-build -p:Version=$V -o ./artifacts - dotnet pack src/analyticsLibrary.Algorithms/analyticsLibrary.Algorithms.csproj -c Release --no-build -p:Version=$V -o ./artifacts - dotnet pack src/analyticsLibrary.Statistics/analyticsLibrary.Statistics.csproj -c Release --no-build -p:Version=$V -o ./artifacts - dotnet pack src/analyticsLibrary.Excel/analyticsLibrary.Excel.csproj -c Release --no-build -p:Version=$V -o ./artifacts - dotnet pack src/analyticsLibrary.Access/analyticsLibrary.Access.csproj -c Release --no-build -p:Version=$V -o ./artifacts - dotnet pack src/analyticsLibrary.Hadoop/analyticsLibrary.Hadoop.csproj -c Release --no-build -p:Version=$V -o ./artifacts - dotnet pack src/analyticsLibrary/analyticsLibrary.csproj -c Release --no-build -p:Version=$V -o ./artifacts + dotnet pack src/analyticsLibrary.Core/analyticsLibrary.Core.csproj -c Release --no-build "-p:Version=$V" -o ./artifacts + dotnet pack src/analyticsLibrary.Algorithms/analyticsLibrary.Algorithms.csproj -c Release --no-build "-p:Version=$V" -o ./artifacts + dotnet pack src/analyticsLibrary.Statistics/analyticsLibrary.Statistics.csproj -c Release --no-build "-p:Version=$V" -o ./artifacts + dotnet pack src/analyticsLibrary.Excel/analyticsLibrary.Excel.csproj -c Release --no-build "-p:Version=$V" -o ./artifacts + dotnet pack src/analyticsLibrary.Access/analyticsLibrary.Access.csproj -c Release --no-build "-p:Version=$V" -o ./artifacts + dotnet pack src/analyticsLibrary.Hadoop/analyticsLibrary.Hadoop.csproj -c Release --no-build "-p:Version=$V" -o ./artifacts + dotnet pack src/analyticsLibrary/analyticsLibrary.csproj -c Release --no-build "-p:Version=$V" -o ./artifacts - name: Inspect packages run: | @@ -84,7 +116,7 @@ jobs: - name: Deploy to NuGet run: | dotnet nuget push ./artifacts/*.nupkg \ - --api-key ${{ secrets.NUGET_API_KEY }} \ + --api-key "${{ secrets.NUGET_API_KEY }}" \ --source https://api.nuget.org/v3/index.json \ --skip-duplicate diff --git a/src/analyticsLibrary.Access/access.cs b/src/analyticsLibrary.Access/access.cs index 026fc0a..4fefd5f 100644 --- a/src/analyticsLibrary.Access/access.cs +++ b/src/analyticsLibrary.Access/access.cs @@ -6,13 +6,13 @@ namespace analyticsLibrary.Access { - public class Access + public class access { - public string filePath { get; private set; } + public string filePath { get; private set; } - public Access(string filePath) => this.filePath = filePath; + public access(string filePath) => this.filePath = filePath; - private OleDbConnection _connection; + private OleDbConnection _connection; private OleDbConnection connection { @@ -37,7 +37,7 @@ public IEnumerable execute(string sql) return results.Rows.Cast(); } - public IEnumerable
tablesByName(string tableName = null) + public IEnumerable
tablesByName(string tableName = null) { var results = (IEnumerable)null; @@ -54,7 +54,7 @@ public IEnumerable
tablesByName(string tableName = null) } return results? - .Select(r => new Table() + .Select(r => new table() { schema = r.Field("TABLE_SCHEMA"), name = r.Field("TABLE_NAME"), @@ -62,7 +62,7 @@ public IEnumerable
tablesByName(string tableName = null) .Where(r => tableName == null || (tableName != null && r.name.ToLower().Contains(tableName.ToLower()))); } - private IEnumerable columns() + private IEnumerable columns() { var results = (IEnumerable)null; @@ -79,7 +79,7 @@ private IEnumerable columns() } return results? - .Select(c => new Column() + .Select(c => new column() { parentTable = c["table_name"].ToString(), name = c["column_name"].ToString(), @@ -87,15 +87,15 @@ private IEnumerable columns() length = c.IsNull("character_maximum_length") ? (int?)null : int.Parse(c["character_maximum_length"].ToString()), nullable = c["is_nullable"].ToString().ToUpper() == "TRUE" ? true : false, }); - } + } - public IEnumerable columnsByTable(string tableName = null) + public IEnumerable columnsByTable(string tableName = null) { return columns() ?.Where(r => tableName == null || (tableName != null && r.parentTable.ToLower().Contains(tableName.ToLower()))); - } + } - public IEnumerable columnsByName(string columnName = null) + public IEnumerable columnsByName(string columnName = null) { return columns() ?.Where(r => columnName == null || (columnName != null && r.name.ToLower().Contains(columnName.ToLower()))); diff --git a/src/analyticsLibrary.Algorithms/searching.cs b/src/analyticsLibrary.Algorithms/searching.cs index dc3e2a7..eda0d01 100644 --- a/src/analyticsLibrary.Algorithms/searching.cs +++ b/src/analyticsLibrary.Algorithms/searching.cs @@ -6,7 +6,7 @@ namespace analyticsLibrary.Algorithms { - public static class Searching + public static class searching { private static bool stringSearch(object value1, object value2) => string.Compare(value1.ToString(), value2.ToString()) <= 0; diff --git a/src/analyticsLibrary.Algorithms/sorting.cs b/src/analyticsLibrary.Algorithms/sorting.cs index 1338428..63d7c06 100644 --- a/src/analyticsLibrary.Algorithms/sorting.cs +++ b/src/analyticsLibrary.Algorithms/sorting.cs @@ -4,7 +4,7 @@ namespace analyticsLibrary.Algorithms { - public static class Sorting + public static class sorting { private static bool stringLess(object value1, object value2) => string.Compare(value1.ToString(), value2.ToString()) <= 0; diff --git a/src/analyticsLibrary.Core/analytics.cs b/src/analyticsLibrary.Core/analytics.cs index 91f5da1..3df329b 100644 --- a/src/analyticsLibrary.Core/analytics.cs +++ b/src/analyticsLibrary.Core/analytics.cs @@ -4,7 +4,7 @@ namespace analyticsLibrary.Core { - public static class Analytics + public static class analytics { public static string compress(this string value, string compress) => Regex.Replace(value, string.Format("([{0}]+)", compress), compress); diff --git a/src/analyticsLibrary.Core/column.cs b/src/analyticsLibrary.Core/column.cs index 527192d..97ee1e0 100644 --- a/src/analyticsLibrary.Core/column.cs +++ b/src/analyticsLibrary.Core/column.cs @@ -3,7 +3,7 @@ namespace analyticsLibrary.Core { [Serializable] - public class Column + public class column { public string parentTable { get; set; } public string name { get; set; } diff --git a/src/analyticsLibrary.Core/csv.cs b/src/analyticsLibrary.Core/csv.cs index af26f41..4d13ed3 100644 --- a/src/analyticsLibrary.Core/csv.cs +++ b/src/analyticsLibrary.Core/csv.cs @@ -5,7 +5,7 @@ namespace analyticsLibrary.Core { - public class Csv : IKeyIndex + public class csv : IKeyIndex { private string _file { get; set; } private char _delimeter = ','; @@ -40,19 +40,19 @@ public int[] fieldMaxLengths private bool _hasHeader; - public Csv(string file) : + public csv(string file) : this(file, ',', true) { } - public Csv(string file, char delimiter) : + public csv(string file, char delimiter) : this(file, delimiter, true) { } - public Csv(string file, bool hasHeader = true) : + public csv(string file, bool hasHeader = true) : this(file, ',', hasHeader) { } - public Csv(string file, char delimeter, bool hasHeader = true) + public csv(string file, char delimeter, bool hasHeader = true) { this._delimeter = delimeter; this._file = file; @@ -84,8 +84,8 @@ private void buildHeader() data.Length < 100 ? "00" : data.Length < 1000 ? - "000" : - //else + "000" : + //else "0"; _header = data @@ -126,22 +126,22 @@ public bool keyExists(string key, out int index) return false; } - private List> _data; + private List> _data; - public IEnumerable> data + public IEnumerable> data { get { if (_header == null) buildHeader(); if (_data == null) { - _data = new List>(); + _data = new List>(); var stream = new StreamReader(this._file); var addHeader = _hasHeader; while (stream.Peek() > -1) { - var record = new Data(this, stream.ReadLine().fromCsv(_delimeter)); + var record = new data(this, stream.ReadLine().fromCsv(_delimeter)); if (addHeader) { diff --git a/src/analyticsLibrary.Core/csvStream.cs b/src/analyticsLibrary.Core/csvStream.cs index f50315e..7cd6107 100644 --- a/src/analyticsLibrary.Core/csvStream.cs +++ b/src/analyticsLibrary.Core/csvStream.cs @@ -1,130 +1,130 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.IO; -using System.Linq; - -namespace analyticsLibrary.Core -{ - public class csvStream : IKeyIndex, IDisposable - { - private string _file { get; set; } - private char _delimeter = ','; - - private bool _hasHeader; - - private StreamReader _stream; - - public csvStream(string file) : - this(file, ',', true) - { } - - public csvStream(string file, char delimiter) : - this(file, delimiter, true) - { } - - public csvStream(string file, bool hasHeader = true) : - this(file, ',', hasHeader) - { } - - public csvStream(string file, char delimeter, bool hasHeader = true) - { - this._delimeter = delimeter; - this._file = file; - this._hasHeader = hasHeader; - this._stream = new StreamReader(this._file); - } - - private indexObject[] _header_lower_case; - private indexObject[] _header; - - private void buildHeader() - { - if (_header == null) - { - var headerStream = new StreamReader(this._file); - var firstRow = headerStream.ReadLine().fromCsv(_delimeter); - if (_hasHeader) - { - _header = firstRow.index(); - for (int i = 0; i < _header.Length; i++) - _header[i].value = _header[i].value ?? $"Column{_header[i].index + 1}"; - } - else - { - var record = firstRow - .index(); - - var format = record.Length < 10 ? - "0" : - record.Length < 100 ? - "00" : - record.Length < 1000 ? +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; + +namespace analyticsLibrary.Core +{ + public class csvStream : IKeyIndex, IDisposable + { + private string _file { get; set; } + private char _delimeter = ','; + + private bool _hasHeader; + + private StreamReader _stream; + + public csvStream(string file) : + this(file, ',', true) + { } + + public csvStream(string file, char delimiter) : + this(file, delimiter, true) + { } + + public csvStream(string file, bool hasHeader = true) : + this(file, ',', hasHeader) + { } + + public csvStream(string file, char delimeter, bool hasHeader = true) + { + this._delimeter = delimeter; + this._file = file; + this._hasHeader = hasHeader; + this._stream = new StreamReader(this._file); + } + + private indexObject[] _header_lower_case; + private indexObject[] _header; + + private void buildHeader() + { + if (_header == null) + { + var headerStream = new StreamReader(this._file); + var firstRow = headerStream.ReadLine().fromCsv(_delimeter); + if (_hasHeader) + { + _header = firstRow.index(); + for (int i = 0; i < _header.Length; i++) + _header[i].value = _header[i].value ?? $"Column{_header[i].index + 1}"; + } + else + { + var record = firstRow + .index(); + + var format = record.Length < 10 ? + "0" : + record.Length < 100 ? + "00" : + record.Length < 1000 ? "000" : //else - "0"; - - _header = record - .Select(r => $"Column{(r.index + 1).ToString(format)}") - .index(); - } - headerStream.Close(); - _header_lower_case = _header.Select(h => h.value.ToLower()).index(); - } - } - - public IEnumerable header - { - get - { - buildHeader(); - return _header.Select(h => h.value); - } - } - - public bool keyExists(string key) => keyExists(key, out var index); - - public bool keyExists(string key, out int index) - { - buildHeader(); - var field = _header_lower_case.SingleOrDefault(v => v.value == key.ToLower()); - if (field != null) - { - index = field.index; - return true; - } - index = -1; - return false; - } - - public bool endOfFile - { - get => _stream.EndOfStream; + "0"; + + _header = record + .Select(r => $"Column{(r.index + 1).ToString(format)}") + .index(); + } + headerStream.Close(); + _header_lower_case = _header.Select(h => h.value.ToLower()).index(); + } + } + + public IEnumerable header + { + get + { + buildHeader(); + return _header.Select(h => h.value); + } } + public bool keyExists(string key) => keyExists(key, out var index); + + public bool keyExists(string key, out int index) + { + buildHeader(); + var field = _header_lower_case.SingleOrDefault(v => v.value == key.ToLower()); + if (field != null) + { + index = field.index; + return true; + } + index = -1; + return false; + } + + public bool endOfFile + { + get => _stream.EndOfStream; + } + private int _recordCount = 1; - + public int recordCount { - get => _recordCount; - private set => _recordCount = value; - } - - public Data nextRecord - { - get - { - if (_header == null) buildHeader(); - if (!endOfFile) - { - var data = new Data(this, _stream.ReadLine().fromCsv(this._delimeter)); + get => _recordCount; + private set => _recordCount = value; + } + + public data nextRecord + { + get + { + if (_header == null) buildHeader(); + if (!endOfFile) + { + var data = new data(this, _stream.ReadLine().fromCsv(this._delimeter)); if (recordCount++ == 1 && _hasHeader) - data = new Data(this, _stream.ReadLine().fromCsv(this._delimeter)); - + data = new data(this, _stream.ReadLine().fromCsv(this._delimeter)); + return data; - } - - return default(Data); + } + + return default(data); } } @@ -139,6 +139,6 @@ public void Dispose() { this._stream.Close(); this._stream.Dispose(); - } - } + } + } } \ No newline at end of file diff --git a/src/analyticsLibrary.Core/csv_base.cs b/src/analyticsLibrary.Core/csv_base.cs index 1a8afaa..b777695 100644 --- a/src/analyticsLibrary.Core/csv_base.cs +++ b/src/analyticsLibrary.Core/csv_base.cs @@ -125,22 +125,22 @@ public bool keyExists(string key, out int index) return false; } - protected List> _records; + protected List> _records; - public IEnumerable> records + public IEnumerable> records { get { if (_header == null) buildHeader(); if (_records == null) { - _records = new List>(); + _records = new List>(); var stream = new StreamReader(this._file); var addHeader = _hasHeader; while (stream.Peek() > -1) { - var record = new Data(this, stream.ReadLine().fromCsv(_delimeter)); + var record = new data(this, stream.ReadLine().fromCsv(_delimeter)); if (addHeader) { diff --git a/src/analyticsLibrary.Core/data.cs b/src/analyticsLibrary.Core/data.cs index a27938e..f23709c 100644 --- a/src/analyticsLibrary.Core/data.cs +++ b/src/analyticsLibrary.Core/data.cs @@ -3,11 +3,11 @@ namespace analyticsLibrary.Core { - public class Data + public class data { internal IKeyIndex parent { get; private set; } - public Data(IKeyIndex parent, TValue[] values) + public data(IKeyIndex parent, TValue[] values) { this.parent = parent; this.values = values; diff --git a/src/analyticsLibrary.Core/extensions.cs b/src/analyticsLibrary.Core/extensions.cs index 055d71b..4251af2 100644 --- a/src/analyticsLibrary.Core/extensions.cs +++ b/src/analyticsLibrary.Core/extensions.cs @@ -10,7 +10,7 @@ namespace analyticsLibrary.Core { - public static class Extensions + public static class extensions { public static string stringOrNull(this object value, int length) { diff --git a/src/analyticsLibrary.Core/fixedWidthFile.cs b/src/analyticsLibrary.Core/fixedWidthFile.cs index ccbdda0..5f950cf 100644 --- a/src/analyticsLibrary.Core/fixedWidthFile.cs +++ b/src/analyticsLibrary.Core/fixedWidthFile.cs @@ -95,22 +95,22 @@ public bool keyExists(string key, out int index) return false; } - protected List> _records; + protected List> _records; - public IEnumerable> records + public IEnumerable> records { get { if (_header == null) buildHeader(); if (_records == null) { - _records = new List>(); + _records = new List>(); var stream = new StreamReader(this._file); var addHeader = _hasHeader; while (stream.Peek() > -1) { - var record = new Data(this, stream.ReadLine().splitFromPositions(_positions)); + var record = new data(this, stream.ReadLine().splitFromPositions(_positions)); if (addHeader) { diff --git a/src/analyticsLibrary.Core/fixedWidthFileStream.cs b/src/analyticsLibrary.Core/fixedWidthFileStream.cs index da62627..fd955dc 100644 --- a/src/analyticsLibrary.Core/fixedWidthFileStream.cs +++ b/src/analyticsLibrary.Core/fixedWidthFileStream.cs @@ -29,7 +29,7 @@ public fixedWidthFileStream(string file, int[] positions, bool hasHeader = true) this._positions = positions; this._file = file; this._hasHeader = hasHeader; - this._stream = new StreamReader(this._file); + this._stream = new StreamReader(this._file); } protected indexObject[] _header_lower_case; @@ -57,8 +57,8 @@ protected void buildHeader() record.Length < 100 ? "00" : record.Length < 1000 ? - "000" : - //else + "000" : + //else "0"; _header = record @@ -99,24 +99,24 @@ public bool endOfFile get => _stream.EndOfStream; } - public int recordCount { get; private set; } + public int recordCount { get; private set; } - public Data nextRecord + public data nextRecord { get { if (_header == null) buildHeader(); if (!endOfFile) - { - var record = new Data(this, _stream.ReadLine().splitFromPositions(_positions)); + { + var record = new data(this, _stream.ReadLine().splitFromPositions(_positions)); if (recordCount++ == 0 && _hasHeader) - record = new Data(this, _stream.ReadLine().splitFromPositions(_positions)); + record = new data(this, _stream.ReadLine().splitFromPositions(_positions)); return record; } - return default(Data); + return default(data); } } diff --git a/src/analyticsLibrary.Core/login.cs b/src/analyticsLibrary.Core/login.cs index 0bab2e3..8b81048 100644 --- a/src/analyticsLibrary.Core/login.cs +++ b/src/analyticsLibrary.Core/login.cs @@ -1,6 +1,6 @@ namespace analyticsLibrary.Core { - public class Login + public class login { public string dsn { get; set; } public int port { get; set; } diff --git a/src/analyticsLibrary.Core/presentation.cs b/src/analyticsLibrary.Core/presentation.cs index e00d90d..d47bc01 100644 --- a/src/analyticsLibrary.Core/presentation.cs +++ b/src/analyticsLibrary.Core/presentation.cs @@ -7,7 +7,7 @@ namespace analyticsLibrary.Core { - public static class Presentation + public static class presentation { private const string hexColor = @"^#(((?[a-fA-F\d]{2}?)(?[a-fA-F\d]{2}?)(?[a-fA-F\d]{2}?))|((?[a-fA-F\d]{1}?)(?[a-fA-F\d]{1}?)(?[a-fA-F\d]{1}?)))$"; private static Regex colorCheck = new Regex(hexColor, RegexOptions.Singleline | RegexOptions.Compiled); diff --git a/src/analyticsLibrary.Core/table.cs b/src/analyticsLibrary.Core/table.cs index fcc3666..aa9f3ec 100644 --- a/src/analyticsLibrary.Core/table.cs +++ b/src/analyticsLibrary.Core/table.cs @@ -4,11 +4,11 @@ namespace analyticsLibrary.Core { [Serializable] - public class Table + public class table { public string schema { get; set; } public string name { get; set; } - public IEnumerable columns { get; set; } + public IEnumerable columns { get; set; } } } diff --git a/src/analyticsLibrary.Core/utility.cs b/src/analyticsLibrary.Core/utility.cs index 693971c..6f4607c 100644 --- a/src/analyticsLibrary.Core/utility.cs +++ b/src/analyticsLibrary.Core/utility.cs @@ -4,7 +4,7 @@ namespace analyticsLibrary.Core { - internal static class Utility + internal static class utility { internal static string assemblyDirectory { diff --git a/src/analyticsLibrary.Excel/extensions.cs b/src/analyticsLibrary.Excel/extensions.cs index 143cb05..c5e38f6 100644 --- a/src/analyticsLibrary.Excel/extensions.cs +++ b/src/analyticsLibrary.Excel/extensions.cs @@ -4,7 +4,7 @@ using analyticsLibrary.Core; namespace analyticsLibrary.Excel { - public static class Extensions + public static class extensions { public static int columnNumber(this columnLettersEnum value) { @@ -44,14 +44,14 @@ public static void numberFormatColumns(this object sheet, int[] columns, string } } - public static object rowValue(this DataRow row, string field) + public static object rowValue(this DataRow row, string field) { - return row.rowValue(field); + return row.rowValue(field); } - public static TField rowValue(this DataRow row, string field) + public static valueType rowValue(this DataRow row, string field) { var fieldName = string.Empty; - var attributes = typeof(TMember).GetMember(field)[0] + var attributes = typeof(type).GetMember(field)[0] .GetCustomAttributes(true); var attribute = attributes.FirstOrDefault(f => f is sheetColumnAttribute) as sheetColumnAttribute; @@ -66,12 +66,11 @@ public static TField rowValue(this DataRow row, string field) } } } - if (string.IsNullOrWhiteSpace(fieldName)) throw new ColumnNotFoundException(); - if (!row.containsColumn(fieldName)) throw new ColumnNotFoundException(); + if (string.IsNullOrWhiteSpace(fieldName)) throw new columnNotFoundException(); + if (!row.containsColumn(fieldName)) throw new columnNotFoundException(); - return (TField)(row.IsNull(fieldName) ? null : row.Field(fieldName)); + return (valueType)(row.IsNull(fieldName) ? null : row.Field(fieldName)); } + public class columnNotFoundException : ApplicationException { } } - - public class ColumnNotFoundException : ApplicationException { } } diff --git a/src/analyticsLibrary.Hadoop/hadoop.cs b/src/analyticsLibrary.Hadoop/hadoop.cs index 7194689..446199b 100644 --- a/src/analyticsLibrary.Hadoop/hadoop.cs +++ b/src/analyticsLibrary.Hadoop/hadoop.cs @@ -70,10 +70,10 @@ public IEnumerable execute(string sql) return results.Rows.Cast(); } - public IEnumerable
tablesByName(string tableName = null) + public IEnumerable
tablesByName(string tableName = null) { return this.execute("SHOW TABLES") - .Select(r => new Table() + .Select(r => new table() { schema = this.schema, name = r.containsColumn("tab_name") ? r.Field("tab_name") : r.Field("name"), @@ -82,7 +82,7 @@ public IEnumerable
tablesByName(string tableName = null) string.IsNullOrWhiteSpace(tableName) || (!string.IsNullOrWhiteSpace(tableName) && t.name.ToLower().Contains(tableName))); } - public IEnumerable columnsByTable(string tableName) + public IEnumerable columnsByTable(string tableName) { var typeRegEx = @"(?[A-z\d_]+>?)(\((?[\d]+?)\))*"; return execute($"describe {tableName}") @@ -91,7 +91,7 @@ public IEnumerable columnsByTable(string tableName) var matches = Regex.Match( c.containsColumn("data_type") ? c.Field("data_type") : c.Field("type"), typeRegEx, RegexOptions.IgnoreCase); - return new Column() + return new column() { parentTable = tableName, name = c.containsColumn("col_name") ? c.Field("col_name") : c.Field("name"), diff --git a/src/analyticsLibrary.Statistics/extensions.cs b/src/analyticsLibrary.Statistics/extensions.cs index 6ef8aca..aefabfe 100644 --- a/src/analyticsLibrary.Statistics/extensions.cs +++ b/src/analyticsLibrary.Statistics/extensions.cs @@ -5,7 +5,7 @@ namespace analyticsLibrary.Statistics { - public static class Extensions + public static class extensions { public static double standardDeviation(this int[] values) { diff --git a/tests/analyticsLibrary.Algorithms.Tests/AlgorithmsTests.cs b/tests/analyticsLibrary.Algorithms.Tests/AlgorithmsTests.cs index accd5e4..cbc10a1 100644 --- a/tests/analyticsLibrary.Algorithms.Tests/AlgorithmsTests.cs +++ b/tests/analyticsLibrary.Algorithms.Tests/AlgorithmsTests.cs @@ -86,7 +86,7 @@ public class SortingPickFunctionTests [Fact] public void PickFunction_Int_ReturnsLessOrEqualForAscending() { - var fn = Sorting.pickFunction(descending: false); + var fn = sorting.pickFunction(descending: false); Assert.True(fn(1, 2)); Assert.True(fn(2, 2)); Assert.False(fn(3, 2)); @@ -95,7 +95,7 @@ public void PickFunction_Int_ReturnsLessOrEqualForAscending() [Fact] public void PickFunction_Double_ReturnsExpectedComparison() { - var fn = Sorting.pickFunction(descending: false); + var fn = sorting.pickFunction(descending: false); Assert.True(fn(1.5, 2.5)); Assert.False(fn(2.5, 1.5)); } @@ -103,7 +103,7 @@ public void PickFunction_Double_ReturnsExpectedComparison() [Fact] public void PickFunction_String_ComparesLexicographically() { - var fn = Sorting.pickFunction(descending: false); + var fn = sorting.pickFunction(descending: false); Assert.True(fn("apple", "banana")); Assert.False(fn("zebra", "ant")); } diff --git a/tests/analyticsLibrary.Core.Tests/CoreTests.cs b/tests/analyticsLibrary.Core.Tests/CoreTests.cs index 29f679c..83d4311 100644 --- a/tests/analyticsLibrary.Core.Tests/CoreTests.cs +++ b/tests/analyticsLibrary.Core.Tests/CoreTests.cs @@ -111,7 +111,7 @@ public void WriteCsv_ThenRead_PreservesData() rows.writeCsv(tempFile, header, ','); - var csvReader = new Csv(tempFile); + var csvReader = new csv(tempFile); var data = csvReader.data.ToList(); Assert.Equal(2, data.Count); diff --git a/tests/analyticsLibrary.Excel.Tests/ExcelTests.cs b/tests/analyticsLibrary.Excel.Tests/ExcelTests.cs index e09c37c..3f271b0 100644 --- a/tests/analyticsLibrary.Excel.Tests/ExcelTests.cs +++ b/tests/analyticsLibrary.Excel.Tests/ExcelTests.cs @@ -64,7 +64,7 @@ public void RowValue_Returns_Value_And_Throws_When_Missing() row2["Other"] = "x"; dt2.Rows.Add(row2); - Assert.Throws(() => dt2.Rows[0].rowValue("ColA")); + Assert.Throws(() => dt2.Rows[0].rowValue("ColA")); } [Fact] From 5ed15469a6276ff4dea502bea0929ce3fc8a7532 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 19:16:34 +0000 Subject: [PATCH 6/6] fix: clarify release version comparison guard Agent-Logs-Url: https://github.com/hpractv/analyticsLibrary/sessions/a451a23a-a4a9-47a7-9dd4-fa7930371322 Co-authored-by: hpractv <13870231+hpractv@users.noreply.github.com> --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4aefa58..6d71b00 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,8 +71,8 @@ jobs: exit 1 fi - lower=$(printf '%s\n%s\n' "$V" "$current" | sort -V | head -n1) - if [ "$lower" != "$current" ] && [ "$V" != "$current" ]; then + max_version=$(printf '%s\n%s\n' "$V" "$current" | sort -V | tail -n1) + if [ "$max_version" != "$V" ]; then echo "ERROR: Requested release version $V is lower than project version $current in $csproj" exit 1 fi