fix(tools): improve build script discovery and update Verifier to System.CommandLine v8+

Build script:
- Add Get-RepoRelativePath() helper for cross-platform path handling
- Exclude node_modules and bin/obj from solution discovery

Verifier:
- Replace deprecated SetHandler with SetAction handler pattern
- Use GetRequiredValue/GetValue instead of GetValueForOption
- Replace SetDefaultValue with DefaultValueFactory property
- Remove CommandLineBuilder wrapper (built into framework now)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
master
2026-03-09 07:53:08 +02:00
parent e6094e3b53
commit e0c79e0dc0
4 changed files with 82 additions and 62 deletions

View File

@@ -31,8 +31,29 @@ $ErrorActionPreference = 'Continue'
$repoRoot = Split-Path -Parent $PSScriptRoot
$srcDir = Join-Path $repoRoot 'src'
function Get-RepoRelativePath {
param(
[Parameter(Mandatory = $true)]
[string]$Root,
[Parameter(Mandatory = $true)]
[string]$Path
)
$normalizedRoot = [System.IO.Path]::GetFullPath($Root).TrimEnd('\', '/')
$normalizedPath = [System.IO.Path]::GetFullPath($Path)
if ($normalizedPath.StartsWith($normalizedRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
return $normalizedPath.Substring($normalizedRoot.Length).TrimStart('\', '/')
}
return $normalizedPath
}
$solutions = Get-ChildItem -Path $srcDir -Filter '*.sln' -Recurse |
Where-Object { $_.Name -ne 'StellaOps.sln' } |
Where-Object {
$_.Name -ne 'StellaOps.sln' -and
$_.FullName -notmatch '[\\/](node_modules|bin|obj)[\\/]'
} |
Sort-Object FullName
if ($solutions.Count -eq 0) {
@@ -50,7 +71,7 @@ $testFail = @()
$testSkipped = @()
foreach ($sln in $solutions) {
$rel = [System.IO.Path]::GetRelativePath($repoRoot, $sln.FullName)
$rel = Get-RepoRelativePath -Root $repoRoot -Path $sln.FullName
Write-Host "--- BUILD: $rel ---" -ForegroundColor Yellow
dotnet build $sln.FullName --configuration $Configuration --nologo -v quiet

View File

@@ -13,13 +13,11 @@
using StellaOps.Verifier;
using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Parsing;
var bundleOption = new Option<FileInfo>("--bundle", ["-b"])
{
Description = "Path to the evidence bundle to verify",
IsRequired = true
Required = true
};
var trustedKeysOption = new Option<FileInfo?>("--trusted-keys", ["-k"])
@@ -39,33 +37,33 @@ var outputOption = new Option<FileInfo?>("--output", ["-o"])
var formatOption = new Option<ReportFormat>("--format", ["-f"])
{
Description = "Report output format"
Description = "Report output format",
DefaultValueFactory = _ => ReportFormat.Markdown
};
formatOption.SetDefaultValue(ReportFormat.Markdown);
var verifySignaturesOption = new Option<bool>("--verify-signatures")
{
Description = "Verify bundle manifest signatures"
Description = "Verify bundle manifest signatures",
DefaultValueFactory = _ => true
};
verifySignaturesOption.SetDefaultValue(true);
var verifyTimestampsOption = new Option<bool>("--verify-timestamps")
{
Description = "Verify RFC 3161 timestamps"
Description = "Verify RFC 3161 timestamps",
DefaultValueFactory = _ => true
};
verifyTimestampsOption.SetDefaultValue(true);
var verifyDigestsOption = new Option<bool>("--verify-digests")
{
Description = "Verify blob digests"
Description = "Verify blob digests",
DefaultValueFactory = _ => true
};
verifyDigestsOption.SetDefaultValue(true);
var verifyPairsOption = new Option<bool>("--verify-pairs")
{
Description = "Verify pair artifacts (SBOM, delta-sig)"
Description = "Verify pair artifacts (SBOM, delta-sig)",
DefaultValueFactory = _ => true
};
verifyPairsOption.SetDefaultValue(true);
var quietOption = new Option<bool>("--quiet", ["-q"])
{
@@ -92,19 +90,19 @@ var verifyCommand = new Command("verify", "Verify an evidence bundle")
verboseOption
};
verifyCommand.SetHandler(async (context) =>
verifyCommand.SetAction(async (parseResult, ct) =>
{
var bundle = context.ParseResult.GetValueForOption(bundleOption)!;
var trustedKeys = context.ParseResult.GetValueForOption(trustedKeysOption);
var trustProfile = context.ParseResult.GetValueForOption(trustProfileOption);
var output = context.ParseResult.GetValueForOption(outputOption);
var format = context.ParseResult.GetValueForOption(formatOption);
var verifySignatures = context.ParseResult.GetValueForOption(verifySignaturesOption);
var verifyTimestamps = context.ParseResult.GetValueForOption(verifyTimestampsOption);
var verifyDigests = context.ParseResult.GetValueForOption(verifyDigestsOption);
var verifyPairs = context.ParseResult.GetValueForOption(verifyPairsOption);
var quiet = context.ParseResult.GetValueForOption(quietOption);
var verbose = context.ParseResult.GetValueForOption(verboseOption);
var bundle = parseResult.GetRequiredValue(bundleOption);
var trustedKeys = parseResult.GetValue(trustedKeysOption);
var trustProfile = parseResult.GetValue(trustProfileOption);
var output = parseResult.GetValue(outputOption);
var format = parseResult.GetValue(formatOption);
var verifySignatures = parseResult.GetValue(verifySignaturesOption);
var verifyTimestamps = parseResult.GetValue(verifyTimestampsOption);
var verifyDigests = parseResult.GetValue(verifyDigestsOption);
var verifyPairs = parseResult.GetValue(verifyPairsOption);
var quiet = parseResult.GetValue(quietOption);
var verbose = parseResult.GetValue(verboseOption);
var options = new VerifierOptions
{
@@ -122,8 +120,7 @@ verifyCommand.SetHandler(async (context) =>
};
var verifier = new BundleVerifier();
var exitCode = await verifier.VerifyAsync(options, context.GetCancellationToken());
context.ExitCode = exitCode;
return await verifier.VerifyAsync(options, ct);
});
var infoCommand = new Command("info", "Display bundle information without verification")
@@ -133,19 +130,18 @@ var infoCommand = new Command("info", "Display bundle information without verifi
quietOption
};
infoCommand.SetHandler(async (context) =>
infoCommand.SetAction(async (parseResult, ct) =>
{
var bundle = context.ParseResult.GetValueForOption(bundleOption)!;
var format = context.ParseResult.GetValueForOption(formatOption);
var quiet = context.ParseResult.GetValueForOption(quietOption);
var bundle = parseResult.GetRequiredValue(bundleOption);
var format = parseResult.GetValue(formatOption);
var quiet = parseResult.GetValue(quietOption);
var verifier = new BundleVerifier();
var exitCode = await verifier.ShowInfoAsync(
return await verifier.ShowInfoAsync(
bundle.FullName,
format,
quiet,
context.GetCancellationToken());
context.ExitCode = exitCode;
ct);
});
var rootCommand = new RootCommand("Stella Ops Bundle Verifier - Offline evidence bundle verification")
@@ -154,16 +150,17 @@ var rootCommand = new RootCommand("Stella Ops Bundle Verifier - Offline evidence
infoCommand
};
// Add version option
rootCommand.AddOption(new Option<bool>("--version", "Show version information"));
var parser = new CommandLineBuilder(rootCommand)
.UseDefaults()
.UseExceptionHandler((ex, context) =>
{
Console.Error.WriteLine($"Error: {ex.Message}");
context.ExitCode = 2;
})
.Build();
return await parser.InvokeAsync(args);
try
{
return await rootCommand.Parse(args).InvokeAsync();
}
catch (OperationCanceledException)
{
Console.Error.WriteLine("Verification cancelled.");
return 2;
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error: {ex.Message}");
return 2;
}

View File

@@ -20,14 +20,6 @@
<Product>Stella Ops Bundle Verifier</Product>
<Description>Standalone verifier for Stella Ops evidence bundles</Description>
<!-- Single-file and self-contained publishing -->
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<!-- Optimize for size -->
<InvariantGlobalization>true</InvariantGlobalization>
<DebuggerSupport>false</DebuggerSupport>
@@ -37,6 +29,14 @@
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
</PropertyGroup>
<PropertyGroup Condition="'$(PublishSingleFile)' == 'true'">
<SelfContained>true</SelfContained>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
</PropertyGroup>
<!-- Runtime identifiers for cross-platform builds -->
<PropertyGroup Condition="'$(RuntimeIdentifier)' == ''">
<RuntimeIdentifiers>win-x64;linux-x64;linux-musl-x64;osx-x64;osx-arm64</RuntimeIdentifiers>
@@ -46,6 +46,12 @@
<PackageReference Include="System.CommandLine" />
</ItemGroup>
<ItemGroup>
<Compile Remove="__Tests\**\*.cs" />
<EmbeddedResource Remove="__Tests\**\*" />
<None Remove="__Tests\**\*" />
</ItemGroup>
<!-- Minimal dependencies for standalone operation -->
<ItemGroup>
<!-- No database, network, or heavy framework dependencies -->

View File

@@ -13,16 +13,12 @@
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>