Skip to content
Merged
Show file tree
Hide file tree
Changes from 71 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
c4b9557
Re-sync protected config + workflow files to canonical (C1 drift)
Chris-Wolfgang May 22, 2026
3965c94
Hoist <Nullable>enable</Nullable> to Directory.Build.props
Chris-Wolfgang May 22, 2026
14c19cc
Scope the nullable hoist so it cannot break legacy / F# projects
Chris-Wolfgang May 22, 2026
6063abd
Enable CodeQL security-extended query pack (S1)
Chris-Wolfgang May 23, 2026
bcb404b
Add Stryker mutation-testing workflow (T3)
Chris-Wolfgang May 23, 2026
fb6ad11
Verify documentation builds before publishing release (D8)
Chris-Wolfgang May 23, 2026
ab19f94
Verify previous versions preserved in docfx versions.json (D6)
Chris-Wolfgang May 23, 2026
7aebf15
Scaffold PublicApiAnalyzers infrastructure (A1)
Chris-Wolfgang May 23, 2026
b1b3e60
Canonical NuGet package metadata + SourceLink + symbol packages (CI3)
Chris-Wolfgang May 23, 2026
da126a5
Publish code-coverage report to docs/coverage/ (T1)
Chris-Wolfgang May 23, 2026
705d4fd
Add BenchmarkDotNet → gh-pages chart workflow (P2)
Chris-Wolfgang May 23, 2026
34c861c
Address PR review round 2 (canonical-protected)
Chris-Wolfgang May 23, 2026
f10c6fc
Address PR review round 2 (t3-stryker)
Chris-Wolfgang May 23, 2026
ffbf3d0
Fold T3 (stryker mutation-testing workflow) into canonical-protected
Chris-Wolfgang May 23, 2026
03db5c0
Tighten <Nullable> condition to SDK-style C# projects only
Chris-Wolfgang May 24, 2026
4ccb202
Add SDK-aware explanatory comment to <Nullable> property
Chris-Wolfgang May 24, 2026
a14dd19
Pin stryker.yaml's upload-artifact to v7 to match rest of fleet
Chris-Wolfgang May 24, 2026
8d26fea
Drop -UseBasicParsing from Invoke-WebRequest in docfx.yaml
Chris-Wolfgang May 24, 2026
482cea4
Make ReportGenerator install idempotent in docfx.yaml T1 step
Chris-Wolfgang May 24, 2026
13e5b12
Align .gitattributes *.ps1 with .editorconfig (LF, not CRLF)
Chris-Wolfgang May 25, 2026
aa0df7a
Add coverlet.runsettings to match repo-template
Chris-Wolfgang May 25, 2026
d2a81a0
Detect .slnx solutions in build-all-versions.yaml
Chris-Wolfgang May 25, 2026
166545f
Filter versions.json to tags whose docs were actually built
Chris-Wolfgang May 25, 2026
c13a2d6
D6 preservation guard: only treat 404 as first deploy
Chris-Wolfgang May 25, 2026
56c706a
Disable persisted credentials in stryker.yaml checkout
Chris-Wolfgang May 25, 2026
c11bc0b
Make dotnet-stryker install idempotent in stryker.yaml
Chris-Wolfgang May 25, 2026
99bf028
Gate dotnet workload restore on workload-bearing TFM detection
Chris-Wolfgang May 25, 2026
1d90e13
release.yaml: explicit Out-File -Encoding utf8 + DocFX shell: pwsh
Chris-Wolfgang May 25, 2026
c267c0d
docfx.yaml T1 coverage: pass --settings coverlet.runsettings
Chris-Wolfgang May 25, 2026
230a4dd
Gate verify-docs-build on validate-release + pack-and-validate
Chris-Wolfgang May 25, 2026
85c732c
Gate gh-pages root cleanup on DEPLOY_AS_LATEST=true
Chris-Wolfgang May 25, 2026
079209c
Drop -windows from workload-TFM detection regex
Chris-Wolfgang May 25, 2026
217f3ee
verify-docs-build: install full SDK set to match validate-release
Chris-Wolfgang May 25, 2026
45ccbf9
D6 guard: skip on dry-runs (inputs.deploy_to_pages == false)
Chris-Wolfgang May 25, 2026
3452ad2
Fix invalid PowerShell in docfx.yaml SemVer prerelease comparator
Chris-Wolfgang May 25, 2026
eed78d8
Protected-file guard: detect deletions too (--diff-filter=AMRCD)
Chris-Wolfgang May 25, 2026
008ddc6
Exclude *.Tests.Integration.* from pr.yaml test-discovery loops
Chris-Wolfgang May 25, 2026
1bd7d81
Fail job when protected-config fetch/copy fails
Chris-Wolfgang May 25, 2026
33d4967
Gitleaks fetch: abort on transient failure, fall back only on missing…
Chris-Wolfgang May 25, 2026
68375d4
verify-docs-build: gated dotnet workload restore before restore/build
Chris-Wolfgang May 25, 2026
d717864
pr.yaml: drop duplicated "configuration files from main" header bullet
Chris-Wolfgang May 25, 2026
71195b6
Stage 3 coverage gate: skip when no coverage files were produced
Chris-Wolfgang May 25, 2026
d9bdbe4
Stage 2 coverage parse: accept extra columns + fail on zero matches
Chris-Wolfgang May 25, 2026
2492006
dotnet test: add --no-build --no-restore in all stages
Chris-Wolfgang May 25, 2026
e6ababf
stryker.yaml: drop dead/broken configs<<EOF output
Chris-Wolfgang May 25, 2026
9301b32
Remove duplicated NuGet metadata defaults from per-project csprojs
Chris-Wolfgang May 25, 2026
0109a38
docfx.yaml: align D6 comment with the actual fetch source
Chris-Wolfgang May 25, 2026
fe2cfd4
Sync workload-TFM comment with regex (drop -windows mention)
Chris-Wolfgang May 25, 2026
e4df4de
docfx.yaml T1 coverage: pin to a single TFM (net10.0)
Chris-Wolfgang May 25, 2026
7e26e81
pr.yaml: fail (not skip) when ./tests is missing in a repo with src/
Chris-Wolfgang May 25, 2026
a3fd88b
Repair pr.yaml after broken Stage 2 coverage-regex fan-out
Chris-Wolfgang May 25, 2026
749be57
build-all-versions.yaml: pipe to Out-Host, not Write-Host
Chris-Wolfgang May 25, 2026
71e45eb
pr.yaml: run protected-config copy loop in parent shell, not subshell
Chris-Wolfgang May 25, 2026
60581da
Stage 2 coverage regex: use greedy match to capture LAST percent
Chris-Wolfgang May 25, 2026
2047ee9
Repair pr.yaml after second \$'-token corruption of Stage 2 regex
Chris-Wolfgang May 25, 2026
cbb6f03
Strip year-specific <Copyright> overrides from src csprojs
Chris-Wolfgang May 25, 2026
c01a2b8
D6 preservation guard: fail when newly-generated versions.json is mis…
Chris-Wolfgang May 25, 2026
cc8cd73
release.yaml: attach .snupkg symbol packages alongside .nupkg
Chris-Wolfgang May 25, 2026
8d1299e
docfx.yaml deploy: precise exit-code handling on diff/commit/push
Chris-Wolfgang May 25, 2026
7dd5d9f
stryker.yaml: run BOTH root and per-test-project configs
Chris-Wolfgang May 25, 2026
e13395c
Stage 1 (Linux) coverage parser: fail when 0 modules match
Chris-Wolfgang May 25, 2026
186687d
pr.yaml trusted-config-fetch: three correctness fixes
Chris-Wolfgang May 25, 2026
65653e7
docfx.yaml deploy: clear versions/<dir> and versions/latest before copy
Chris-Wolfgang May 25, 2026
7f4a93e
docfx coverage: add --no-restore to dotnet test
Chris-Wolfgang May 25, 2026
7ebafa5
D6 guard: also skip when deploy_as_latest is false (rebuild mode)
Chris-Wolfgang May 25, 2026
9d8d66a
D6 guard: emit ::error:: via Write-Host so Actions parses the annotation
Chris-Wolfgang May 25, 2026
4f3fcae
Stage 1 coverage parser: accept decimal percents + floor before compare
Chris-Wolfgang May 25, 2026
9137e45
pr.yaml: fix Stage 2 coverage parser — greedy .* turned 100% into 0%
Chris-Wolfgang May 26, 2026
fd90586
Bump coverlet.collector from 10.0.0 to 10.0.1
dependabot[bot] May 24, 2026
e68b6d0
Bump Meziantou.Analyzer from 3.0.85 to 3.0.93
dependabot[bot] May 24, 2026
e0f3bf6
Bump SonarAnalyzer.CSharp from 10.24.0.138807 to 10.26.0.140279
dependabot[bot] May 24, 2026
96ba9df
pr.yaml: add coverlet.runsettings to trusted-config protection lists
Chris-Wolfgang May 29, 2026
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
13 changes: 6 additions & 7 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,12 @@ indent_size = 2
[*.{yml,yaml}]
indent_size = 2

# PowerShell files
# PowerShell uses CRLF to maintain compatibility with Windows and PowerShell conventions
# This overrides the global end_of_line = lf setting and aligns with .gitattributes line 14
[*.ps1]
indent_size = 4
end_of_line = crlf
charset = utf-8-bom
# PowerShell scripts inherit LF + UTF-8 (no BOM) + 4-space indent from
# the global [*] section above — no per-language override is needed.
# LF + no-BOM is required for the `#!/usr/bin/env pwsh` shebang (where
# present) on scripts under scripts/ to work on Linux/macOS — CR
# breaks the kernel's exec lookup, and a leading BOM prevents shebang
# recognition entirely.
Comment thread
Chris-Wolfgang marked this conversation as resolved.
Comment thread
Chris-Wolfgang marked this conversation as resolved.
Comment thread
Chris-Wolfgang marked this conversation as resolved.
Comment thread
Chris-Wolfgang marked this conversation as resolved.
Comment thread
Chris-Wolfgang marked this conversation as resolved.
Comment thread
Chris-Wolfgang marked this conversation as resolved.
Comment thread
Chris-Wolfgang marked this conversation as resolved.
Comment thread
Chris-Wolfgang marked this conversation as resolved.
Comment thread
Chris-Wolfgang marked this conversation as resolved.

# C# files
[*.cs]
Expand Down
11 changes: 6 additions & 5 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
*.fsx text eol=lf

# Scripts
# PowerShell scripts: CRLF line endings (intentional override)
# Both .gitattributes and .editorconfig consistently configure PowerShell files
# to use CRLF (Windows-style) line endings for PowerShell convention compliance.
# See .editorconfig [*.ps1] section for the matching configuration.
*.ps1 text eol=crlf
# PowerShell scripts: LF line endings (no override beyond the * text=auto eol=lf above).
# Required so the `#!/usr/bin/env pwsh` shebang on scripts under scripts/ works
# on Linux/macOS — CR breaks the kernel's exec lookup, and a leading BOM
# prevents shebang recognition entirely. Matches the .editorconfig [*] section,
# which intentionally has no [*.ps1] override.
*.ps1 text eol=lf


# Build and configuration files
Expand Down
86 changes: 86 additions & 0 deletions .github/workflows/benchmarks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: Benchmarks

# Runs BenchmarkDotNet after a merge to main, then publishes the results to
# the gh-pages branch under /dev/bench/ where benchmark-action/github-action-benchmark
# renders an interactive line chart.
Comment thread
Chris-Wolfgang marked this conversation as resolved.
#
# This workflow is decoupled from PR validation and release: it does NOT gate
# merging or publishing — it just adds one data point per qualifying push.

on:
push:
branches: [main]
paths:
- 'src/**'
- 'benchmarks/**'
- '.github/workflows/benchmarks.yaml'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

jobs:
benchmark:
name: Run BenchmarkDotNet & publish chart
runs-on: ubuntu-latest

permissions:
contents: write

steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
persist-credentials: false

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
8.0.x

- name: Restore
run: dotnet restore benchmarks/Wolfgang.Extensions.IEnumerable.Benchmarks/Wolfgang.Extensions.IEnumerable.Benchmarks.csproj

- name: Build (Release)
run: dotnet build -c Release --no-restore benchmarks/Wolfgang.Extensions.IEnumerable.Benchmarks/Wolfgang.Extensions.IEnumerable.Benchmarks.csproj

- name: Run benchmarks
working-directory: benchmarks/Wolfgang.Extensions.IEnumerable.Benchmarks
run: dotnet run -c Release --no-build -- --filter "*" --job short --memory --exporters json

- name: Merge per-class BDN reports
id: locate
working-directory: benchmarks/Wolfgang.Extensions.IEnumerable.Benchmarks
run: |
shopt -s nullglob
reports=(BenchmarkDotNet.Artifacts/results/*-report-full-compressed.json)
if [ ${#reports[@]} -eq 0 ]; then
echo "::error::No BenchmarkDotNet JSON report found"
exit 1
fi
echo "Merging ${#reports[@]} report(s):"
printf ' %s\n' "${reports[@]}"
jq -s '{
Title: "BenchmarkDotNet combined report",
HostEnvironmentInfo: .[0].HostEnvironmentInfo,
Benchmarks: [.[].Benchmarks[]]
}' "${reports[@]}" > benchmarks-result.json
echo "report=benchmarks/Wolfgang.Extensions.IEnumerable.Benchmarks/benchmarks-result.json" >> "$GITHUB_OUTPUT"

- name: Publish chart to gh-pages
uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1
with:
name: BenchmarkDotNet
tool: 'benchmarkdotnet'
output-file-path: ${{ steps.locate.outputs.report }}
gh-pages-branch: gh-pages
benchmark-data-dir-path: dev/bench
auto-push: true
github-token: ${{ secrets.GITHUB_TOKEN }}
alert-threshold: '200%'
fail-on-alert: false
27 changes: 20 additions & 7 deletions .github/workflows/build-all-versions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -119,20 +119,20 @@ jobs:
try {
# Attempt dotnet restore + build; failures are non-fatal because
# DocFX can still extract metadata from source files.
$slnFile = Get-ChildItem -Filter '*.sln' -ErrorAction SilentlyContinue |
$slnFile = Get-ChildItem -File -ErrorAction SilentlyContinue | Where-Object { $_.Extension -in '.sln','.slnx' } |
Select-Object -First 1
if ($slnFile) {
Write-Host "Restoring $($slnFile.Name)..."
dotnet restore $slnFile.FullName 2>&1 | Write-Host
dotnet restore $slnFile.FullName 2>&1 | Out-Host
Write-Host "Building $($slnFile.Name)..."
dotnet build $slnFile.FullName --configuration Release --no-restore 2>&1 | Write-Host
dotnet build $slnFile.FullName --configuration Release --no-restore 2>&1 | Out-Host
}

Write-Host "Running docfx metadata..."
docfx metadata docfx_project/docfx.json 2>&1 | Write-Host
docfx metadata docfx_project/docfx.json 2>&1 | Out-Host

Write-Host "Running docfx build..."
docfx build docfx_project/docfx.json 2>&1 | Write-Host
docfx build docfx_project/docfx.json 2>&1 | Out-Host

if (Test-Path 'docfx_project/_site') {
$dest = Join-Path $outDir 'versions' $version
Expand Down Expand Up @@ -207,7 +207,7 @@ jobs:

Push-Location $latestWorkDir
try {
$slnFile = Get-ChildItem -Filter '*.sln' -ErrorAction SilentlyContinue |
$slnFile = Get-ChildItem -File -ErrorAction SilentlyContinue | Where-Object { $_.Extension -in '.sln','.slnx' } |
Select-Object -First 1
if ($slnFile) {
Write-Host "Restoring $($slnFile.Name)..."
Expand Down Expand Up @@ -285,9 +285,22 @@ jobs:
Sort-Object -Property Major, Minor, Patch, Stable -Descending |
Select-Object -ExpandProperty Tag

# Only emit version-picker entries for tags whose docs were actually
# built and copied under $outDir/versions/<tag>/. Skipped tags (worktree
# add failed, DocFX produced no _site, etc.) would otherwise appear in
# versions.json as links to 404s on gh-pages.
$versionsDir = Join-Path $outDir 'versions'
$builtTags = if (Test-Path $versionsDir) {
Get-ChildItem -Path $versionsDir -Directory | Select-Object -ExpandProperty Name
} else { @() }

[array]$versions = @([PSCustomObject]@{ version = 'latest'; url = "${base}versions/latest/" })
foreach ($t in $orderedTags) {
$versions += [PSCustomObject]@{ version = $t; url = "${base}versions/$t/" }
if ($builtTags -contains $t) {
$versions += [PSCustomObject]@{ version = $t; url = "${base}versions/$t/" }
} else {
Write-Host "::notice::Skipping versions.json entry for $t — no built docs under versions/$t/"
}
}

$versionsJson = ConvertTo-Json -InputObject $versions -Depth 3
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/codeql.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,44 @@ jobs:
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# security-extended adds the broader security query pack on top of the
# default queries (more rules, slightly longer scans).
queries: security-extended

- name: Setup .NET
if: steps.check-csharp.outputs.has-csharp == 'true'
uses: actions/setup-dotnet@v5
with:
dotnet-version: '10.0.x'

- name: Restore .NET workloads
if: steps.check-csharp.outputs.has-csharp == 'true'
shell: pwsh
run: |
# Skip entirely if no csproj declares a workload-bearing TFM (android/ios/
# maccatalyst/maui/tvos/tizen/browser) — netX.Y-windows TFMs use
# SDK-bundled projection assemblies, not the workload installer, so
# they're intentionally excluded. Saves ~5-15s on pure-library
# repos and removes a network-dependent failure mode.
Comment thread
Chris-Wolfgang marked this conversation as resolved.
$hasWorkloadTfm = @(Get-ChildItem -Recurse -Filter *.csproj |
Select-String -Pattern 'net\d+\.\d+-(android|ios|maccatalyst|maui|tvos|tizen|browser)' -List).Count -gt 0
if (-not $hasWorkloadTfm) {
Write-Host "No workload-bearing TFMs — skipping dotnet workload restore"
exit 0
}
$solution = Get-ChildItem -Path . -Recurse -Depth 2 -Include "*.sln", "*.slnx" | Select-Object -First 1
if ($solution) {
Write-Host "Restoring workloads for $($solution.FullName)"
dotnet workload restore "$($solution.FullName)"
} else {
Write-Host "No solution found; restoring workloads for all projects"
dotnet workload restore
}
if ($LASTEXITCODE -ne 0) {
Write-Error "dotnet workload restore failed with exit code $LASTEXITCODE"
exit $LASTEXITCODE
}

- name: Build for CodeQL Analysis
id: build
if: steps.check-csharp.outputs.has-csharp == 'true'
Expand Down
Loading
Loading