Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ updates:
- dependencies
- nuget
# -------------------------------------------------------------------------
# These two carry deliberate per-TFM floors (see the csproj comment): a
# These two carry deliberate per-TFM floors (see Directory.Packages.props): a
# net8.0 consumer must stay on its own 8.0.x servicing line, so an
# 8.x -> 10.x major PR is never mergeable and would just be weekly noise.
#
Expand Down
14 changes: 13 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,20 @@ jobs:
# Tests run across every shipped TFM (STANDARD.md 2.3). No --no-build: this job
# does not share a filesystem with `build`, and rebuilding is cheaper and less
# fragile than shipping obj/ between jobs.
# `-- --coverage` passes through to Microsoft.Testing.Platform's coverage
# extension (STANDARD.md 2.6). Referencing a collector without invoking it is
# worse than none: it reads as coverage in the dependency list while producing
# no data. One .coverage file lands per TFM, under bin/*/TestResults.
- name: Test
run: dotnet test --configuration Release --verbosity normal
run: dotnet test --configuration Release --verbosity normal -- --coverage

- name: Upload coverage
if: always()
uses: actions/upload-artifact@v7
with:
name: coverage-${{ matrix.os }}
path: '**/TestResults/*.coverage'
if-no-files-found: warn

# THE required status check. Aggregates everything above so the ruleset never has to
# know the matrix shape. `if: always()` is essential — without it the gate is skipped
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **`.github/dependabot.yml` rewritten.** Minor and patch bumps are grouped into one PR per ecosystem; majors are deliberately left ungrouped so each arrives separately and stays open for review. The two runtime-aligned packages carrying per-TFM floors (`Microsoft.Extensions.DependencyInjection.Abstractions`, `Microsoft.Extensions.Http`) are now under `ignore` for major updates, because an 8.x → 10.x bump on the net8 floor is never mergeable and was weekly noise.
- **`global.json` now pins the SDK**, not just the Microsoft.Testing.Platform runner: `10.0.100` with `rollForward: latestFeature`. An unpinned SDK means a contributor on an older one gets different analyzer results from CI, and with `TreatWarningsAsErrors` that is a build which fails for them and passes for everyone else.
- **`.gitignore` and `.editorconfig` replaced with the canonical copies.** The `.editorconfig` change is one line that matters: the private-field naming rule had `applicable_kinds = field`, and a `const` *is* a field, so the rule demanded `_nonceSize` for `private const int NonceSize`. An empty `required_modifiers` scopes it to instance fields. Nothing enforces these rules at build time yet (`EnforceCodeStyleInBuild` is off), so this is a no-op for the build today and correct for when it is not.
- **Central Package Management adopted.** Package versions move out of the three project files into a root `Directory.Packages.props`, with `CentralPackageVersionOverrideEnabled=false` so a stray inline `Version=` is a hard build error rather than being silently ignored. The per-TFM floors survive intact — CPM re-evaluates the conditional `ItemGroup`s per target framework during the inner build, so `net8.0` still floors at 8.0.2 / 8.0.1 and `net10.0` at 10.0.11. **Verified behaviour-preserving:** packed before and after from clean `obj/` at the same commit, the `.nuspec`, both `lib/` assemblies, both XML documentation files, both symbol PDBs, the README and the icon are byte-identical; the only differences in either package are the `.psmdcp` filename, which NuGet regenerates per pack, and the `_rels/.rels` entry that names it. No dependency floor was raised — that is consumer-visible and belongs in its own reviewed commit.
- **Properties every project restated now live in `Directory.Build.props`.** `ImplicitUsings`, `SatelliteResourceLanguages`, `AnalysisLevel`, `GenerateDocumentationFile`, `EnablePackageValidation`, `IncludeSymbols`, `SymbolPackageFormat`, `DebugType`, `PublishRepositoryUrl`, `EmbedUntrackedSources`, `ContinuousIntegrationBuild`, `PackageLicenseExpression` and `Copyright`. Duplicated settings are how these repos drifted apart in the first place; each csproj now carries only what is genuinely specific to it. The demo project opts `GenerateDocumentationFile` back off — it is not shipped, so its types are not public surface and demanding XML docs on sample code would fail the build under `TreatWarningsAsErrors`.

### Added
- **Code coverage is now collected.** Adds `Microsoft.Testing.Extensions.CodeCoverage` and — the part that actually matters — has CI invoke it (`dotnet test … -- --coverage`) and upload the result per platform. A collector that is referenced but never invoked is worse than none: it reads as coverage in the dependency list while producing no data. One `.coverage` file lands per target framework, verified locally on both.

- **`SECURITY.md`**, with a scope section specific to this library: SHA-256 verification establishes integrity but not authenticity (the expected hash ships from the same release as the asset, and there is no signature checking); `AllowInsecureManifestSource` and `UseDefaultSha256Verifier = false` are documented opt-outs that defeat verification by design; archive path-traversal defence is the framework's `ZipFile`/`TarFile` guard rather than this library's; and `GhCliReleaseSource` trusts whatever `gh` is on `PATH`. Stating the boundary is the point — a report that only restates a documented limitation is not a vulnerability.
- **`CONTRIBUTING.md`, `.github/PULL_REQUEST_TEMPLATE.md` and `CLAUDE.md`.** `CLAUDE.md` records the constraints an agent would otherwise violate here — why the three-platform matrix is load-bearing, why one install-lock test returns early on Windows by design, and that `PackageValidationBaselineVersion` tracks the last shipped release.
Expand Down
19 changes: 19 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
<Project>
<!--
STANDARD.md 1.2: every property that is identical across projects lives here
rather than being restated per csproj. Fifteen copies is fifteen chances to
diverge, and that duplication is how these repos drifted apart. A csproj
carries only what is genuinely specific to it.
-->
<PropertyGroup>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<AnalysisLevel>latest</AnalysisLevel>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnablePackageValidation>true</EnablePackageValidation>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<DebugType>portable</DebugType>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">true</ContinuousIntegrationBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Copyright>© Stuart Meeks</Copyright>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Authors>Stuart Meeks</Authors>
<Company>Next Iteration</Company>
Expand Down
78 changes: 78 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<Project>

<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<!--
Hard-fail if a csproj declares <PackageReference Version="…" />
alongside CPM. Without this MSBuild silently ignores the inline
version and uses the central one — which is the exact drift this
file exists to prevent. STANDARD.md 1.3.
-->
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
</PropertyGroup>

<!--
Runtime-aligned Microsoft platform deps: per-TFM floors. STANDARD.md 1.4.

In a library a PackageReference version is a *minimum* NuGet forces on
every downstream consumer (lowest-applicable-version resolution). Flooring
these at a single high major would drag net8 LTS consumers off their own
runtime-aligned servicing line onto the net10 one. Each target framework
therefore floors at its own major. CPM re-evaluates these conditional
groups per-TFM during the inner build.

net8.0 sits at 8.0.2 / 8.0.1 because those are the final 8.0.x servicing
versions — there is nothing further to move to on that line.

Both are also listed under `ignore` in .github/dependabot.yml with
update-types: [version-update:semver-major] (STANDARD.md 4.10), so
Dependabot never proposes the 8.x -> 10.x bump that can never be merged.

Versions here are unchanged from the per-project references they replaced.
This migration is deliberately behaviour-preserving: raising a floor is a
consumer-visible change and belongs in its own reviewed commit.
-->
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.11" />
</ItemGroup>

<!--
Not runtime-aligned: a single common floor at the version actually built
and tested against. Spectre.Console is pre-1.0 — breaking changes land
between minors, so a low floor would be both meaningless and risky.
STANDARD.md 1.5.

Spectre.Console.Cli's latest stable is 0.55.0 (no 0.56/0.57 exists); it
requires Spectre.Console >= 0.55.0, which 0.57.2 satisfies.

Spectre.Console.Testing is test-only but tracks Spectre.Console's version
exactly — it is the same repo and the pair must not drift apart.
-->
<ItemGroup>
<PackageVersion Include="Spectre.Console" Version="0.57.2" />
<PackageVersion Include="Spectre.Console.Cli" Version="0.55.0" />
<PackageVersion Include="Spectre.Console.Testing" Version="0.57.2" />
</ItemGroup>

<!-- Build / source-link tooling. PrivateAssets="All" in the csproj. STANDARD.md 1.7. -->
<ItemGroup>
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.400" />
</ItemGroup>

<!--
Test-only. STANDARD.md 2.1/2.5/2.6 — xunit.v3 on Microsoft.Testing.Platform.
Do NOT add Microsoft.NET.Test.Sdk, xunit.runner.visualstudio or
coverlet.collector: all three are VSTest-only and MTP replaces the runner,
and the .NET 10 SDK no longer supports testing through the VSTest bridge.
-->
<ItemGroup>
<PackageVersion Include="xunit.v3" Version="4.0.0" />
<PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" Version="18.10.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<!--
Not shipped, so its types are not public surface. The root
Directory.Build.props turns GenerateDocumentationFile on for the
package; leaving it on here would demand XML docs on sample code and,
with TreatWarningsAsErrors, fail the build for it. Same reasoning as
the test project (STANDARD.md 2.7).
-->
<GenerateDocumentationFile>false</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,72 +1,63 @@
<Project Sdk="Microsoft.NET.Sdk">

<!--
Shared properties live in the root Directory.Build.props and package
versions in Directory.Packages.props (STANDARD.md 1.2 / 1.3). What is
left here is what is genuinely specific to this project.
-->
<PropertyGroup>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AnalysisLevel>latest</AnalysisLevel>
</PropertyGroup>

<PropertyGroup>
<PackageId>NextIteration.SpectreConsole.SelfUpdate</PackageId>
<Version>0.3.1</Version>
<Authors>Stuart Meeks</Authors>
<Description>Self-update for Spectre.Console CLIs: pluggable update sources (GitHub Releases over HTTP, GitHub Releases via gh CLI for private repos, generic HTTPS manifest, custom), SHA-256 verification, atomic file swap, and a drop-in `update` command.</Description>
<GeneratePackageOnBuild Condition="'$(Configuration)' == 'Release'">true</GeneratePackageOnBuild>
<PackageOutputPath>$(MSBuildThisFileDirectory)..\..\artifacts\packages</PackageOutputPath>
<IncludeBuildOutput>true</IncludeBuildOutput>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageProjectUrl>https://github.com/StuartMeeks/NextIteration.SpectreConsole.SelfUpdate</PackageProjectUrl>
<RepositoryUrl>https://github.com/StuartMeeks/NextIteration.SpectreConsole.SelfUpdate.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>spectre;cli;self-update;auto-update;github-releases</PackageTags>
<PackageIcon>icon.png</PackageIcon>
<Copyright>© Stuart Meeks</Copyright>
<EnablePackageValidation>true</EnablePackageValidation>
<!--
Validate the public surface against the last shipped release, not just
framework compatibility: an accidental API break relative to 0.3.0
fails the build instead of shipping.
-->
<PackageValidationBaselineVersion>0.3.0</PackageValidationBaselineVersion>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<DebugType>portable</DebugType>
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">true</ContinuousIntegrationBuild>
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="NextIteration.SpectreConsole.SelfUpdate.Tests" />
</ItemGroup>

<ItemGroup>
<!-- Common to all target frameworks. -->
<PackageReference Include="Spectre.Console" />
<PackageReference Include="Spectre.Console.Cli" />
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
<None Include="icon.png" Pack="true" PackagePath="" />
</ItemGroup>

<!--
Runtime-aligned Microsoft platform deps are floored per-TFM. A library's
PackageReference version is a floor NuGet forces on every consumer, so a
single 10.0.x floor would drag net8 consumers off their own 8.0.x LTS
servicing line. Each target floors these at the latest stable servicing
version of its own major.
servicing line. The versions themselves live in Directory.Packages.props,
which re-evaluates these conditions per-TFM during the inner build.
-->
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Http" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.11" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.11" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Spectre.Console" Version="0.57.2" />
<PackageReference Include="Spectre.Console.Cli" Version="0.55.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.400" PrivateAssets="All" />
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
<None Include="icon.png" Pack="true" PackagePath="" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Http" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
<PropertyGroup>
<!--
Multi-targets both TFMs the library ships so the net8.0 assets in the
package are actually exercised, not just compiled.
package are actually exercised, not just compiled. STANDARD.md 2.3.
-->
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<!-- xUnit v3 test projects are self-executing host processes. -->
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
Expand All @@ -35,8 +33,15 @@
is pointed at MTP by the `test.runner` setting in global.json.
-->
<ItemGroup>
<PackageReference Include="xunit.v3" Version="4.0.0" />
<PackageReference Include="Spectre.Console.Testing" Version="0.57.2" />
<PackageReference Include="xunit.v3" />
<PackageReference Include="Spectre.Console.Testing" />
<!--
STANDARD.md 2.6: the MTP coverage collector. CI invokes it by passing
the coverage flag through to the test host. A collector that is
referenced but never invoked is worse than none — it reads as coverage
in the dependency list while producing no data.
-->
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" />
</ItemGroup>

<ItemGroup>
Expand Down