diff --git a/.github/workflows/build-dotnet.yml b/.github/workflows/build-dotnet.yml index 226e9fb..16cc1df 100644 --- a/.github/workflows/build-dotnet.yml +++ b/.github/workflows/build-dotnet.yml @@ -40,17 +40,24 @@ jobs: with: fetch-depth: 0 - # The solution references sibling repositories by relative path (../ploch-common and - # ../mrploch-development) - the workspace layout where every mrploch repo is cloned next - # to the others. Reproduce that layout beside the workspace, otherwise restore fails with - # MSB3202 for every ../ploch-common project. Cloning the siblings (rather than checking - # this repository out into a named sub-directory) keeps this repository at the workspace - # root, which is what the Codacy reporter and SonarScanner both expect. + # Reproduce the workspace layout beside the checkout: every mrploch repo cloned next to + # the others. Cloning the siblings (rather than checking this repository out into a named + # sub-directory) keeps this repository at the workspace root, which is what the Codacy + # reporter and SonarScanner both expect. + # + # mrploch-development is required unconditionally: Directory.Packages.props imports the + # shared version files from it. + # + # ploch-common is required by exactly ONE step - "Build sample application", which runs + # -p:UsePlochProjectReferences=true so a library change cannot break the sample silently. + # The main solution restore no longer needs it: in the default package mode the Ploch + # dependencies come from nuget.org, so the old MSB3202-on-every-ploch-common-project + # rationale no longer applies. Whether this clone should go away entirely is #51. # # ploch-common is cloned in full because it uses Nerdbank.GitVersioning too, and NBGV # cannot compute a version height from a shallow clone. - name: Clone ploch-common (referenced projects) - run: git clone https://github.com/mrploch/ploch-common.git --branch master ../ploch-common + run: git clone https://github.com/mrploch/ploch-common.git --branch main ../ploch-common - name: Clone mrploch-development (shared build config) run: git clone --depth 1 https://github.com/mrploch/mrploch-development.git ../mrploch-development @@ -179,8 +186,25 @@ jobs: # between begin and end, and the sample is deliberately excluded from analysis (it also # already sits in sonar.coverage.exclusions). Not continue-on-error - a broken sample is a # broken build. + # + # -p:GeneratePackageOnBuild=false is defence in depth, not a fix for a live bug. In + # UsePlochProjectReferences mode this step pulls the four library projects in as + # ProjectReferences, and they set GeneratePackageOnBuild=true, so in principle they could + # pack a second time - over the package-mode nupkgs this workflow later publishes - with + # ploch-common resolved as a ProjectReference and therefore a PRERELEASE dependency + # version. That is exactly the #47 defect this branch exists to remove. + # + # It does not currently happen: measured on a fully clean tree, this step packs the + # libraries into bin/Debug, while publish-nuget-packages.sh globs */bin/Release/*.nupkg, + # so the published artefacts are untouched. But the only thing preventing it is an + # incidental configuration-mapping quirk - the library projects are not members of the + # sample solution, so they do not inherit its Release mapping. Adding them to that + # solution would silently arm the bug. Suppressing the pack removes the class outright. + # + # Raised by the Copilot (Grok 4.6) reviewer on PR #52 as a live defect; investigated and + # refuted independently by the Codex reviewer; refutation confirmed by measurement. - name: Build sample application - run: dotnet build ./samples/SampleApp/Ploch.CommandLine.Spectre.SampleApp.slnx -c Release -p:UsePlochProjectReferences=true + run: dotnet build ./samples/SampleApp/Ploch.CommandLine.Spectre.SampleApp.slnx -c Release -p:UsePlochProjectReferences=true -p:GeneratePackageOnBuild=false # The main ruleset has a code_coverage rule, which reads coverage GitHub itself holds - # SonarCloud's and Codacy's copies are invisible to it. actions/upload-code-coverage takes a diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 390bc13..6a048b9 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -26,9 +26,12 @@ jobs: url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - # The solution references sibling repositories by relative path - the workspace layout where - # every mrploch repo is cloned next to the others. Reproduce that layout here, otherwise the - # docfx metadata pass fails with MSB3202 for every ../ploch-common project. + # Reproduce the workspace layout here - every mrploch repo cloned next to the others. + # mrploch-development is required: Directory.Packages.props imports the shared version + # files from it. The ploch-common clone below is now belt-and-braces: in the default + # package mode the docfx metadata pass resolves Ploch dependencies from nuget.org, so the + # old MSB3202-on-every-ploch-common-project rationale no longer applies. Whether it can + # be dropped is #51. - name: Checkout uses: actions/checkout@v4 with: @@ -39,7 +42,7 @@ jobs: uses: actions/checkout@v4 with: repository: mrploch/ploch-common - ref: master + ref: main path: ploch-common fetch-depth: 0 @@ -65,6 +68,13 @@ jobs: - name: Build documentation site working-directory: ploch-commandline + # Scoped to this step, not the job, so the package credential is not exposed to + # checkout, upload or deployment actions (least privilege, raised by the CodeRabbit + # reviewer on PR #52). nuget.config maps Ploch.* to both nuget.org and the + # authenticated GitHub Packages feed; without the token that feed 401s on every Ploch + # package and the nuget.org fallback is a race. Full reasoning in release.yml. + env: + GH_PACKAGES_TOKEN: ${{ secrets.GH_PACKAGES_TOKEN }} run: dotnet docfx DocumentationSite/docfx.json - name: Upload artifact diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml index 3c2fce5..3153fea 100644 --- a/.github/workflows/qodana_code_quality.yml +++ b/.github/workflows/qodana_code_quality.yml @@ -26,7 +26,7 @@ jobs: - uses: actions/checkout@v4 with: repository: mrploch/ploch-common - ref: master + ref: main path: ploch-common fetch-depth: 0 - uses: actions/checkout@v4 @@ -63,5 +63,9 @@ jobs: with: pr-mode: false env: + # Scoped to this step, not the job (least privilege, raised by the CodeRabbit reviewer + # on PR #52). The scan restores the solution inside its container, which resolves + # Ploch.* from the authenticated GitHub Packages feed. Full reasoning in release.yml. + GH_PACKAGES_TOKEN: ${{ secrets.GH_PACKAGES_TOKEN }} QODANA_TOKEN: ${{ secrets.QODANA_TOKEN_657107159 }} QODANA_ENDPOINT: 'https://qodana.cloud' \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cad917f..f69112d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,8 +27,9 @@ jobs: env: RELEASE_VERSION: ${{ inputs.release_version }} NEXT_VERSION_INPUT: ${{ inputs.next_version }} - # Every `run` step operates inside the checked-out repository. Sibling repositories are - # checked out next to it, reproducing the workspace layout the solution expects. + # Every `run` step operates inside the checked-out repository. mrploch-development - the + # only sibling this workflow still needs - is checked out next to it, reproducing the + # workspace layout Directory.Packages.props expects. defaults: run: working-directory: ploch-commandline @@ -68,6 +69,32 @@ jobs: fi echo "GH_TOKEN is valid" + # Validate GH_PACKAGES_TOKEN too. Assigning it is not the same as having it: an unset or + # expired secret still leaves nuget.config sending an empty password to the GitHub + # Packages feed, which 401s and puts the restore back in the NU1301 race described on the + # job env above - the exact failure the token was added to remove. Fail here, with a + # readable message, rather than in the middle of a restore. + # Raised by the Copilot (Grok 4.6) reviewer on PR #52. + - name: Validate GH_PACKAGES_TOKEN secret + working-directory: . + env: + GH_PACKAGES_TOKEN: ${{ secrets.GH_PACKAGES_TOKEN }} + run: | + if [ -z "$GH_PACKAGES_TOKEN" ]; then + echo "::error::GH_PACKAGES_TOKEN secret is not set. A token with 'read:packages' scope is required so the GitHub Packages feed mapped for Ploch.* in nuget.config can be authenticated." + exit 1 + fi + # A presence check is not enough: an expired token, or one without read:packages, + # is non-empty and still 401s during restore - which is the failure this step + # exists to pre-empt. So actually authenticate against the feed nuget.config uses. + # Basic auth with the token as the password is how NuGet presents it. + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -u "x-access-token:$GH_PACKAGES_TOKEN" https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json) + if [ "$HTTP_STATUS" != "200" ]; then + echo "::error::GH_PACKAGES_TOKEN is invalid, expired, or lacks 'read:packages' (HTTP $HTTP_STATUS from the GitHub Packages NuGet index). Regenerate the token and update the repository secret." + exit 1 + fi + echo "GH_PACKAGES_TOKEN authenticates against GitHub Packages" + # Use the default GITHUB_TOKEN for checkout - it always works and has read access. # GH_TOKEN (PAT) is configured separately before push steps, because it is the # only token that can trigger subsequent workflows when pushing commits. @@ -77,13 +104,12 @@ jobs: path: ploch-commandline fetch-depth: 0 - - name: Checkout ploch-common (sibling project references) - uses: actions/checkout@v4 - with: - repository: mrploch/ploch-common - ref: master - path: ploch-common - fetch-depth: 0 + # No ploch-common checkout. The release build consumes ploch-common as released NuGet + # packages (UsePlochProjectReferences defaults to false), so the sibling sources are not + # needed - and checking them out at the moving `master` branch is what made this workflow + # able to pack a stable package with prerelease Ploch.Common dependencies (issue #47). + # mrploch-development is still required: Directory.Packages.props imports the shared + # version files from it. - name: Checkout mrploch-development (shared build config) uses: actions/checkout@v4 @@ -134,6 +160,36 @@ jobs: echo "NuGet package version: $NUGET_VERSION" - name: Restore dependencies + # Scoped to this step rather than the job, so the package credential is not exposed + # to checkout, upload, release or other third-party actions (least privilege, raised + # by the CodeRabbit reviewer on PR #52). + # + # nuget.config maps Ploch.* to both nuget.org and the GitHub Packages feed, and the + # GitHub feed authenticates with %GH_PACKAGES_TOKEN%. Without it, every Ploch restore + # queries that feed unauthenticated and takes a 401. + # + # This is a reliability fix, and the mechanism is worth stating precisely because the + # obvious reading of it is wrong in both directions. An unauthenticated eligible feed + # does not reliably fail - a local test with an isolated packages folder and no token + # warned, retried, fell back to nuget.org and exited 0 - but neither is it merely + # cosmetic. NuGet queries sources concurrently and a restore succeeds as soon as one + # supplies the requested version, while a terminal protocol failure from another source + # is rethrown rather than ignored. Which of those happens first is a race: if the + # GitHub feed exhausts its retries before nuget.org returns the match, restore can fail + # with NU1301 even though the package exists on nuget.org. One green run is a sample of + # that race, not proof it cannot go the other way. + # + # It is also load-bearing, not just defensive: prerelease Ploch builds exist ONLY on the + # GitHub feed, so the moment the shared Ploch.Packages.props names a prerelease the + # nuget.org fallback finds nothing and restore fails hard with NU1103. + # + # It does NOT fix that for pull requests from forks - GitHub does not expose repository + # secrets to fork PRs at all, so no workflow can authenticate for them. Tracked in #54. + # + # Raised by four PR reviewers (codeant-ai, qodo, copilot, chatgpt-codex) and sharpened + # by the Codex and Antigravity reviewers on PR #52. + env: + GH_PACKAGES_TOKEN: ${{ secrets.GH_PACKAGES_TOKEN }} run: dotnet restore ./Ploch.CommandLine.Spectre.slnx - name: Build (Release) diff --git a/Directory.Build.props b/Directory.Build.props index 8feb96e..0d0d7b2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -78,6 +78,32 @@ default + + + false + + @@ -102,4 +128,33 @@ + + + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props index 1c8019e..efcaf64 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,4 +1,4 @@ - + true @@ -10,7 +10,42 @@ - + + + + + + $(PlochCommonPackagesVersion) + 4.0.47 + + + + diff --git a/Ploch.CommandLine.Spectre.slnx b/Ploch.CommandLine.Spectre.slnx index eb14a9b..87a019b 100644 --- a/Ploch.CommandLine.Spectre.slnx +++ b/Ploch.CommandLine.Spectre.slnx @@ -1,16 +1,4 @@ - - - - - - - - - - - - diff --git a/nuget.config b/nuget.config index e3624d3..a64d2dd 100644 --- a/nuget.config +++ b/nuget.config @@ -7,11 +7,33 @@ - + + diff --git a/samples/SampleApp/Directory.Packages.props b/samples/SampleApp/Directory.Packages.props index facd497..b5bbc00 100644 --- a/samples/SampleApp/Directory.Packages.props +++ b/samples/SampleApp/Directory.Packages.props @@ -1,4 +1,4 @@ - + @@ -30,11 +30,18 @@ - - - + + + diff --git a/src/Spectre/CommandLine.Spectre.FluentValidation/Ploch.CommandLine.Spectre.FluentValidation.csproj b/src/Spectre/CommandLine.Spectre.FluentValidation/Ploch.CommandLine.Spectre.FluentValidation.csproj index bd4627a..c3c3bcb 100644 --- a/src/Spectre/CommandLine.Spectre.FluentValidation/Ploch.CommandLine.Spectre.FluentValidation.csproj +++ b/src/Spectre/CommandLine.Spectre.FluentValidation/Ploch.CommandLine.Spectre.FluentValidation.csproj @@ -8,13 +8,21 @@ + + + + + + + + + - - + diff --git a/src/Spectre/CommandLine.Spectre.Serilog/Ploch.CommandLine.Spectre.Serilog.csproj b/src/Spectre/CommandLine.Spectre.Serilog/Ploch.CommandLine.Spectre.Serilog.csproj index 34c961c..b2f9615 100644 --- a/src/Spectre/CommandLine.Spectre.Serilog/Ploch.CommandLine.Spectre.Serilog.csproj +++ b/src/Spectre/CommandLine.Spectre.Serilog/Ploch.CommandLine.Spectre.Serilog.csproj @@ -21,7 +21,12 @@ - + + + + + + diff --git a/src/Spectre/CommandLine.Spectre/Ploch.CommandLine.Spectre.csproj b/src/Spectre/CommandLine.Spectre/Ploch.CommandLine.Spectre.csproj index 871af3b..411351a 100644 --- a/src/Spectre/CommandLine.Spectre/Ploch.CommandLine.Spectre.csproj +++ b/src/Spectre/CommandLine.Spectre/Ploch.CommandLine.Spectre.csproj @@ -37,10 +37,19 @@ - + + + + + + + + + + diff --git a/tests/Spectre/CommandLine.Spectre.FluentValidation.Tests/Ploch.CommandLine.Spectre.FluentValidation.Tests.csproj b/tests/Spectre/CommandLine.Spectre.FluentValidation.Tests/Ploch.CommandLine.Spectre.FluentValidation.Tests.csproj index 04dedfa..bdad956 100644 --- a/tests/Spectre/CommandLine.Spectre.FluentValidation.Tests/Ploch.CommandLine.Spectre.FluentValidation.Tests.csproj +++ b/tests/Spectre/CommandLine.Spectre.FluentValidation.Tests/Ploch.CommandLine.Spectre.FluentValidation.Tests.csproj @@ -13,8 +13,15 @@ - + + + + + + + + diff --git a/tests/Spectre/CommandLine.Spectre.Serilog.Tests/Ploch.CommandLine.Spectre.Serilog.Tests.csproj b/tests/Spectre/CommandLine.Spectre.Serilog.Tests/Ploch.CommandLine.Spectre.Serilog.Tests.csproj index dffdff3..3a06987 100644 --- a/tests/Spectre/CommandLine.Spectre.Serilog.Tests/Ploch.CommandLine.Spectre.Serilog.Tests.csproj +++ b/tests/Spectre/CommandLine.Spectre.Serilog.Tests/Ploch.CommandLine.Spectre.Serilog.Tests.csproj @@ -13,8 +13,15 @@ - + + + + + + + + diff --git a/tests/Spectre/CommandLine.Spectre.Tests/Ploch.CommandLine.Spectre.Tests.csproj b/tests/Spectre/CommandLine.Spectre.Tests/Ploch.CommandLine.Spectre.Tests.csproj index 3d3a060..64bdb6b 100644 --- a/tests/Spectre/CommandLine.Spectre.Tests/Ploch.CommandLine.Spectre.Tests.csproj +++ b/tests/Spectre/CommandLine.Spectre.Tests/Ploch.CommandLine.Spectre.Tests.csproj @@ -13,8 +13,17 @@ - + + + + + + + + + + diff --git a/tests/Spectre/CommandLine.UseCases.Tests/Ploch.CommandLine.UseCases.Tests.csproj b/tests/Spectre/CommandLine.UseCases.Tests/Ploch.CommandLine.UseCases.Tests.csproj index 6dbbad2..0cd1da4 100644 --- a/tests/Spectre/CommandLine.UseCases.Tests/Ploch.CommandLine.UseCases.Tests.csproj +++ b/tests/Spectre/CommandLine.UseCases.Tests/Ploch.CommandLine.UseCases.Tests.csproj @@ -13,8 +13,15 @@ - + + + + + + + +